Page 8 of 12

Re: Massive memory optimization: flash functions! (+SPI SSD1

PostPosted: Wed Mar 18, 2015 9:15 pm
by dpwhittaker
flashMod is exactly the same as taking each function and putting in its own file, running node.compile on each file, and then waiting to call dofile until the moment you need to call the function. its major benefit is only having one function at a time loaded. And it does this with a memory footprint of around 1kb.

Re: Massive memory optimization: flash functions! (+SPI SSD1

PostPosted: Fri Apr 03, 2015 8:53 am
by dannybloe
Wow, I don't consider myself as dumb but I read your post a couple of times and I really have no clue of what you mean and what I have to do to make your solution work. To me it appears there are many inconsistencies in the text but maybe I just don't get it. :-)

Can you please provide a simple example or describe in a bit more detail how I can use it? I am fighting with heap shortage so I am very interested in getting this to work.


Re: Massive memory optimization: flash functions! (+SPI SSD1

PostPosted: Fri Apr 03, 2015 10:19 am
by Toshi Bass
x2 but I admit I am dumb.

Re: Massive memory optimization: flash functions! (+SPI SSD1

PostPosted: Fri Apr 03, 2015 10:58 pm
by dpwhittaker
Sorry, I guess it probably is confusing having two different versions of the approach in one thread. Here is the simplest usage of the latest version.

First, there's some advanced examples of creating flashMod itself as a flashMod in the oled example attached to the FP, but let's keep it simple. Just put doFile("flashMod.lua") in you init.lua to make the function available.

Then you define a mod like this:

Code: Select alllocal mod = {MOD_NAME = "myMod"}

-- local private = "do not define any variables outside of a function, they will not be available when you load from flash"

function mod:init() --all functions should be referenced as mod:, the colon is important as it makes the self variable available
    self.myVar = "x" --variables should be defined with self<dot> (not colon, that is only for functions)
    self.yourVar = 42 --variables are put on the table here so we can save state without making them local to the file

function mod:myFunc()
    self:yourFunc(self.myVar .. "yz", self.yourVar - 12) -- functions are called with self<colon>, variables with self<dot>

function mod:yourFunc(me, you)
    print("I am " .. me .. " and you are " .. you)

flashMod(mod) -- when you call flashMod with a table, it writes every function in the table to disk

You only execute the file above when you change it. The functions are written (compiled) to the flash so you particularly do NOT want to call this file (with require or dofile) every time you need to load the mod... it will run through your flash write cycles fast and you'll end up with a dead flash chip. You can only write to any given sector 10,000 to 100,000 times before it will no longer reliably store your data.

Then you would use the flashMod like this:

Code: Select alllocal myMod = flashMod("myMod")  --when you call flashMod with a string, it creates an empty table with a metatable
--specifically, it does not actually load anything from flash at this point, just prepares a table that will load on use

myMod:init() -- this table does not have an init function, but the metatable loads it from flash when you try to access the non-existent key "init"
--since we used mod: to define the method, self. inside the method, and myMod: here, self in the method refers to myMod
--i.e.: myMod.myVar = "x" and myMod.yourVar = 42 now

local yourMod = flashMod("myMod") -- now we have another empty table that will load myMod methods on demand
-- yourVar and myVar do not exist on yourMod until you call yourMod:init()

myMod:myFunc() -- don't forget to always use colons between the module and function name to keep the self references working correctly.

What makes this all work is that, after you call myMod:myFunc(), on the very next line of code, nothing references myFunc any more, so the function can be garbage collected until you call it again.

On the other hand, since myFunc calls yourFunc, yourFunc is loaded into heap while myFunc is still in use. So it is important that you keep your function call stack as shallow as possible.

Another important thing to realize is that these functions are completely independent from each other... only the self reference ties them together. In technical terms, no flash function can have upvalues (google it if you want a lesson in dynamic language compilers). In simpler terms, this means that there should only ever be one variable declared outside of a function - mod - and it should only be used to declare the functions and in the flashMod call at the end, but never inside of a function - that is what self and the colons are for.

Finally, since these functions are all independent, you can separate them into multiple files if you wish. Just keep putting the local mod = {MOD_NAME = "myMod"} at the top of every file and flashMod(mod) at the bottom, and all the methods in all the files will be available any time you create a table with flashMod("myMod").

I should probably split the two usages into two separate functions:

define("modName", table)

If you think this would be less confusing, let me know, and I'll get to it when I can.

With all that said, I'm currently considering using Lua on the ESP as a failed experiment, even with this optimization. Particularly for web servers, there are just too many buffers allocated in a multi-resource page load (even just 3 simultaneous requests) to waste memory on a dynamic runtime and the overhead for every single variable that it requires. This approach allowed me to run things I never could run in Lua without it, but compared to native, the performance loss was unacceptable. With esp-httpd just now getting support for upgrading the web server through the web server itself, it is now the platform I am migrating to.