Placing program text into native strings

Posted on
  • I see the new native strings coming up in 1v85 with great interest. On the esp8266 I see the following two options coming up:

    1. increase JSvars from 1023 to ~1280, using 8KB more heap, reducing heap to ~5-8KB, which is going to cause problems on systems that have several network connections or that are receiving "large" amounts of data (large being >3-4KB). The gain would be ~256 more JSvars plus flat strings using fewer JSvars, so it's a bit more than a 25% increase in effective JSvars.

    2. add a feature to write code into the upcoming native strings, which would be malloc'd. This could perhaps be used in specific cases, such as when requiring a module, i.e., only module code might be put there. Also, the network code could be tweaked to put network buffers into native strings. This doesn't increase the total number of JSvars, but it should free up a lot of them. The fact that it uses malloc means that it shares the memory available to the networking stack, so more code means fewer concurrent connections, less code means more connections.

    Thoughts?

    NB: using native strings to access flash is more difficult, I believe, because flash requires 32-bit accesses and there's the issue of managing the writes and erasures of 4KB blocks.

  • using native strings to access flash is more difficult, I believe, because flash requires 32-bit accesses

    Is flash still memory mapped, so is it as easy as just replacing the character read with something like:

    char read(unsigned int addr) {
      return (*(uint32_t*)(addr&~3)) >> ((addr&3)*8);
    }
    

    We'd need to benchmark, but if so it's possible that the string iterator could be changed and it wouldn't really impact performance that much.

    write code into the upcoming native strings, which would be malloc'd

    I don't think this is a good idea given there's not that much RAM - I think it'd be too difficult for most people to use... If code could be used from flash it'd be amazingly useful though?

    the network code could be tweaked to put network buffers into native strings

    What about the opposite? Increase variable storage as per #1, and put network buffers into JS vars as 'flat strings'. The TLS code already has some hacks to handle this: https://github.com/espruino/Espruino/blo­b/master/libs/crypto/mbedtls/config.h#L1­18-L121

    That might give you some issues with the telnet server and reset() but it could be worked around. It'd mean that far more RAM was available to JS if people wanted it.

    It feels like at the moment RAM is just sitting there just in case someone decides to do several concurrent connections. Given the lack of memory it's almost worth trying to reduce the available packet size (maybe also only allow writing of only one connection at a time), so that we can have more guaranteed memory available.

  • Just to add an option #3:

    • Re-write the JsVar code such that instead of jsVars containing all your variables, it is actually a 'cache', with all variables actually stored in Flash.
    • Use jsvLock/jsvUnLock to load variables out of flash and stick them in the cache in RAM. On UnLock, write any changes to Flash (probably as a journal).

    It's what Espruino was originally designed for (hence the lock/unlock), but I never had a need to implement it because all the flash on the MCU ended up being used up with Espruino itself.

    However, an ESP8266 with 512k of free flash is a different prospect. Does it matter if it's not that fast if you can have effectively 512k of RAM.

  • write code into the upcoming native strings, which would be malloc'd

    I don't think this is a good idea given there's not that much RAM - I think it'd be too difficult for most people to use... If code could be used from flash it'd be amazingly useful though?

    I don't understand. There isn't that little RAM either. Currently there's 13KB available. It's just that if you request a web page that has 10KB in it, you might need ~6-7KB just for that (due to TCP window size and a few extra buffers). If I allocate 8KB of these 13KB to JSvars then that's a fixed commitment. Basically an espruino program with little code but lots of network activity will crash reliably. But if I automatically put module code into malloc'ed RAM then there's no fixed commitment. It just means that users that require large modules will need to be more limited on the communication side. What is difficult to use?

    Running code from flash may be an answer, but it does require quite a bit more work. Is there a place where code finds it "final resting place" in a flat string where I could add code to move it to a native string allocated in flash? I'd also need a hook to free it again, I suppose. Or perhaps I could change the way modules work so one can make an http request to load the text of a module into flash and then it's always available? Maybe that's easier.

  • One issue WRT memory usage that I see is that all code loaded by the IDE needs to fit into JSvars 2 times: once for "real" and once for history. For example, I loaded my http test program, which has a top-level sendTemp() function. When I do a trace() I see:

          #15[r1,l2] Name String [2 blocks] "history"        #19[r1,l1] Array(4) [
              #20[r1,l2] Name Integer 0            #16[r1,l1] String [2 blocks] "echo(0);"
              #57[r1,l2] Name Integer 1            #37[r1,l1] String [17 blocks] "setTime(1.450416955487E9);var postUrl=\"http://h.voneicken.com:4567/te­mp\";var http=require(\"http\");var int1;var errCnt=0;var okCnt=0;var run=true;"
              #290[r1,l2] Name Integer 2            #167[r1,l1] String [110 blocks] "function sendTemp(temp,next){var q=url.parse(postUrl+\"?temp=\"+temp);q.m­ethod=\"POST\";q.headers={\"Content-Leng­th\":0};var req=http.request(q,function(resp){resp.o­n(\"data\",function(d){if(d!==\"OK\")con­sole.log(\"Got unexpected response:\",d)});resp.on(\"close\",funct­ion(gotErr){if(!gotErr&&resp.statusCode!­==\"200\")console.log(\"Got HTTP code\",resp.statusCode);if(gotErr||resp.­statusCode!==\"200\")errCnt++;else okCnt++;if(next!==undefined)next()});res­p.on(\"error\",function(err){console.log­(\"HTTP response error: \",\nerr.message)})});req.on(\"error\",f­unction(err){console.log(\"HTTP request error: \",err.message);errCnt++});req.on(\"clos­e\",function(gotErr){console.log(\"HTTP done\")});if(typeof req.end===\"function\")req.end();else{co­nsole.log(\"OOPS, no end function @\"+(okCnt+errCnt));trace(req);clearInte­rval(int1);run=false}}var t=10;int1=setInterval(function(){console­.log(\"*** OK:\"+okCnt,\"ERR:\"+errCnt,process.memo­ry())},1E4);function s(){setTimeout(function(){if(run){t+=.01­;sendTemp(t,s)}},1)}s();"
    

    So that's the full text of the sendTemp function and more in JSvar #167 for 110 blocks, and then further down:

      #166[r1,l2] Name String [2 blocks] "sendTemp"    #163[r1,l1] Function {
          #162[r1,l2] Name Param "temp"         undefined
          #161[r1,l2] Name Param "next"         undefined
          #124[r1,l2] Name String [1 blocks] "ÿcod"        #58[r1,l1] FlatString [66 blocks] "var q=url.parse(postUrl+\"?temp=\"+temp);q.m­ethod=\"POST\";q.headers={\"Content-Leng­th\":0};var req=http.request(q,function(resp){resp.o­n(\"data\",function(d){if(d!==\"OK\")con­sole.log(\"Got unexpected response:\",d)});resp.on(\"close\",funct­ion(gotErr){if(!gotErr&&resp.statusCode!­==\"200\")console.log(\"Got HTTP code\",resp.statusCode);if(gotErr||resp.­statusCode!==\"200\")errCnt++;else okCnt++;if(next!==undefined)next()});res­p.on(\"error\",function(err){console.log­(\"HTTP response error: \",\nerr.message)})});req.on(\"error\",f­unction(err){console.log(\"HTTP request error: \",err.message);errCnt++});req.on(\"clos­e\",function(gotErr){console.log(\"HTTP done\")});if(typeof req.end===\"function\")req.end();else{co­nsole.log(\"OOPS, no end function @\"+(okCnt+errCnt));trace(req);clearInte­rval(int1);run=false}"
    

    Again, the whole function body. Is there a way to disable history when loading code from the IDE?

    Add-on question: is there a way to print out the memory contents when hitting an "Out of Memory!" error? I tried to put a call to jsvTrace() in there, but that doesn't work because jsvTrace needs some free JSvars to write to the console...

  • Mhh, I'm also noticing that anonymous functions are evil from a space perspective. In my little test code I have:

    function sendTemp(temp, next) {
      var q = url.parse(postUrl + "?temp=" + temp);
      q.method = "POST";
      q.headers = {'Content-Length': 0};
      //console.log("REQ:", q);
      var req = http.request(q, function(resp) {
        resp.on('data', function(d) { if (d !== 'OK') console.log("Got unexpected response:", d); });
        resp.on('close', function(gotErr) {
          if (!gotErr && resp.statusCode !== "200") console.log("Got HTTP code", resp.statusCode);
          if (gotErr || resp.statusCode !== "200") errCnt++; else okCnt++;
          if (next !== undefined) next();
        });
        resp.on('error', function(err) { console.log("HTTP response error: ", err.message); });
      });
      ...
    

    Well, so the history has the whole program text as a string. The entire function sendTemp can be found in another string. Then the callback of http.request (the string starting with resp.on('data', ...) can be found as a string in the #onconnect of the http connection array. Within that, the data and the close callbacks can be found yet again in the resp object. So if I look at the if statement f (!gotErr && resp.statusCode !== "200") console.log("Got HTTP code", resp.statusCode); that's present 4 times in memory! Yikes!

  • I was under the impression that the history gets flushed as needed to make room for other stuff...?

    But yes, you can end up with multiple copies of anonymous functions like that, add I understand it - I always have them just call another function if they're not really short, which I think mashes the code nite readable anyway.

  • It's just that if you request a web page that has 10KB in it, you might need ~6-7KB just for that

    Is there any way of getting that down? It seems a bit extreme when we only have 12k for all our other code and variables, and when we're only dealing with ~500 byte packets of data anyway.

    Espruino is designed not to use malloc - so from my point of view, trying to abuse it in order to use malloc to store the data it needs sounds like a really bad idea.

    To stick with what it's good at, it'd be much better to try and allocate buffers within the statically allocated Espruino memory, and to expand the amount that was statically allocated.

    code loaded by the IDE needs to fit into JSvars 2 times: once for "real" and once for history.

    As @DrAzzy says, history gets flushed as soon as memory gets low.

    anonymous functions are evil from a space perspective

    There's an issue open for that in GitHub - it might be possible to store a link to the original function code, and an offset within it.

    However, what then happens in:

    var a = (function() {
      // blah blah blah
      return function() {
      };
    })();
    

    There's going to have to be code that detects when the outer function disappears, and that then splits up the string.

  • Just to add that you could experiment with RESIZABLE_JSVARS in build_platform_config. That will let Espruino allocate variables in blocks using malloc - it's what gets used on Linux.

    It means that it'll allocate only what it needs - although it can't reclaim memory after it has been allocated so I wonder how useful it is.

  • @Gordon I think I'm getting the same "ERROR: Out of Memory!" with full module string showing up in the history like "Modules.removeAllCached(),Modules.addCa­ched('...."

    I'm running on ESP8266. When I copy-paste all module code into the IDE and adjust the example - it runs fine!

  • Try setting minification (in settings) to closure instead of esprima.

    Esprima globs everything together onto one line, and that results in Espruino running out of memory far earlier than it should.

  • The other issue is that modules are loaded as one big string of code. If the modules are too big then there won't be enough memory to hold that string and the module itself when it is loaded.

    There's not much of a way around that (apart from copy/pasting the module code into the main code window), but I imagine that if memory is that tight, you might end up with problems executing it anyway.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Placing program text into native strings

Posted by Avatar for tve @tve

Actions