Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Something must be say about bytearray + readinto #2

Open
dglaude opened this issue Apr 5, 2021 · 3 comments
Open

Something must be say about bytearray + readinto #2

dglaude opened this issue Apr 5, 2021 · 3 comments

Comments

@dglaude
Copy link

dglaude commented Apr 5, 2021

Based on the two recent CircuitPython meeting, it is clear that something bust be said about bytearray.

If you only have to store positive integer from 0 to 255, then that is the optimal structure to use as each entry cost one byte, where a list of int might cost 4 bytes per entry.

So if short table of short value are needed, putting them as bytearray is beneficial, making a list of tuple would be overkill as the list is an object, each tuple is an object and each int take some space.

A typical bad example is this table: https://github.com/adafruit/Adafruit_CircuitPython_IS31FL3731/blob/9993369654a47ca8763b0971e1dc847717b21cc6/adafruit_is31fl3731/keybow2040.py#L67 :

        lookup = [
            (120, 88, 104),  # 0, 0
            (136, 40, 72),  # 1, 0
            (112, 80, 96),  # 2, 0
            (128, 32, 64),  # 3, 0
            (121, 89, 105),  # 0, 1
            (137, 41, 73),  # 1, 1
            (113, 81, 97),  # 2, 1
            (129, 33, 65),  # 3, 1
            (122, 90, 106),  # 0, 2
            (138, 25, 74),  # 1, 2
            (114, 82, 98),  # 2, 2
            (130, 17, 66),  # 3, 2
            (123, 91, 107),  # 0, 3
            (139, 26, 75),  # 1, 3
            (115, 83, 99),  # 2, 3
            (131, 18, 67),  # 3, 3
        ]

All values are between 0 and 255 and with a code a little bit smarter, it can be an array of 48 bytes.

If the needed data are big, then reading them from file into a bytearray rather than to declare that in the code will make the code smaller and the memory usage more efficient. Typically a map for a game (containing reference to background tile) could be into a resource file.

One way to avoid memory activity that has a cost in fragmentation and CPU, it is best to use any function that do "readinto" an existing buffer. And that buffer should be the same everytime we have to read (like when you change level in a game). This mean a local variable in a function allocated on the stack at each call is worst than a global (to the library) variable.

Bad example are like this: https://github.com/adafruit/Adafruit_CircuitPython_MLX90640/blob/2668229b7b72dcfc2317c1e607be8ecb1c505cd9/adafruit_mlx90640.py#L123

Every time this function is called, for every "image" a new temporary array of 834 element is created:

    def getFrame(self, framebuf):
        """Request both 'halves' of a frame from the sensor, merge them
        and calculate the temperature in C for each of 32x24 pixels. Placed
        into the 768-element array passed in!"""
        emissivity = 0.95
        tr = 23.15
        mlx90640Frame = [0] * 834

Since you only make one call at a time to getFrame, it can be a more global and always the same buffer.

Maybe blurring the distinction between memory on the stack and memory in the heap is not great... and I don't exactly know how CP work inside and what is stored where, so this can give general idea on what to do or not to do.

@kmatch98
Copy link
Owner

kmatch98 commented Apr 5, 2021

Cool that helps clear up some about your comment about “use bytearray”. But let me be sure I’m clear.

If you define a big array in code, then it takes up RAM by

  • loading the code to create the array
  • and to hold the array

so you’re suggesting that if you keep the array definition in a file somewhere you can load it into a bytearray and save the RAM that would have been used to hold the code.

Thanks for the “non-memory-usage-optimized” examples. Any “memory-optimized examples? :)

@dglaude
Copy link
Author

dglaude commented Apr 5, 2021

The use bytearray was suggested by danh and match some of my reading on MicroPython memory optimisation per type.

I am totally not sure how an array initialisation in a *.py file consume memory.
It sure consume memory for parsing the *.py file... once I wanted to make a array of 768 "real" number and it exploded. Splitting that one line (sic) into multiple line where each line add element to the array was a trick, ugly trick: https://gist.github.com/dglaude/9eca2d82a2db2660a65c91086dee44e5

But the proper way to do it would be a file with all those value (here I don't know exactly the format, because those are not byte or integer, but floating point things. And read that at runtime.

You can skip the parsing if you use mpy, then maybe it will be directly into the optimal format. My idea is that the "compilation" of a *.py file into a *.mpy file generated bytecode for your "function" and a "data" segment for the (global) variable you create/initialise.

I am totally unsure about "data segment" and "code segment".

I believe the memory usage learn guide should be primarly focused on user writing *.py file... not really library writer.
The assumption is that library code will be reviewed and if it is not memory optimal, then someone will complain and suggest change. So many tricks like mpy-cross would be "advance".

I don't know enough about the internal of CP to give really good advice. Like I don't know if a temporary array you use in a function is on the stack or just a pointer in the stack pointing to something allocated on the heap. I would guess it is the later and that most of the memory problem user have are heap memory issue, not stack (except if they use recursive function).

Also fighting for memory on an "M0" or a Pico is not the same. Actually, on the Pico or the Feather S2.
Should one write the code on a big memory board, measure there, then try to run on a smaller board?
Or write with the "M4" as a target to be "clean" from the start?

Sorry, I don't have link to good examples, maybe because most of the code is not too bad? And I only know the place where I would like to fix it when I have time for that.

@dglaude
Copy link
Author

dglaude commented Apr 5, 2021

Here is the discussion on byte and bytearray and the various way to make that table for the IS32FL3731 library:
adafruit/Adafruit_CircuitPython_IS31FL3731#43

It might help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants