*Edit: this post was originally written when Microvium was around 8.2 kB (rounded up to 8.5 kB). Since then, new features have been added. As of June 2022, Microvium is now 9.04 kB (rounded up to 10 kB). This post has been updated accordingly.
Size often matters in small MCU devices. A large proportion of microcontroller models available on the market still have less than 64 kB of flash and less than 2 kB of RAM. These are still used because they’re smaller, cheaper, and have lower power than their larger counterparts. All the microcontrollers I’ve worked with in my career as a firmware engineer have had ≤ 16 kB RAM.
What about RAM?
The amount of RAM Elk uses is not pre-defined — you give it a buffer of RAM of any size you want, but it needs to be at least 96 bytes for the VM kernel state. Microvium takes 54 bytes for the kernel state.
But where there’s a massive difference in memory requirement is that Elk requires all of its memory allocated upfront, and keeps it for the lifetime of the VM. If your script’s peak memory in Elk is 1 kB then you need to give it a 1 kB buffer at startup, so its idle memory usage is 1 kB. Microvium on the other hand uses malloc and free to allocate when needed and free when not needed. Its idle memory usage can be as low as 36 bytes. In typical firmware, idle memory is much more important than peak memory, as I explained in my last post.
What about the feature set? This is another area where Microvium and Elk diverge significantly. The following table shows the differences:
|Computed member access ||✓|
|Arrow functions, closures||✓|
|Uses intermediate bytecode (better performance)||✓|
|Parser at runtime||✓|
|ROM||10 kB||11.5 kB|
|Idle RAM||36 B||Lots|
|Peak kernel RAM||56 B||96 B|
|Slot size (size of simple variables)||2 B||8 B|
But Cesanta, the maker of Elk, also made a larger JS engine with more features: mJS, which is probably the closest match to Microvium in terms of feature set. mJS lets you write for-loops and switch statements for example.
Since they’re closely matched for intent and features, I did a more detailed comparison of mJS and Microvium here. But here’s a summary:
|Arrow functions and closures||✓|
(but mJS does support a non-standard
|Computed member access ||✓||✓|
|Uses intermediate bytecode (better performance)||✓||✓|
|Parser at runtime||✓||✓|
|ROM||10 kB||45.6 kB||11.5 kB|
|Slot size||2 B||8 B||8 B|
I’ve lumped “some builtin-functions” into one box because it’s not a language feature as such. mJS has a number of builtin functions that Microvium doesn’t have – most notably
Object.create. You can implement these yourself in Microvium quite easily without modifying the engine (or find implementations online), and it gives you the option of choosing what you want rather than having all that space forced on you2.
But the added features in mJS means it costs a lot more in terms of ROM space — about 4x more than Elk and 5x more than Microvium.
Microvium still has more core language features than mJS, making it arguably a more pleasant language to work in. These features are actually quite useful in certain scenarios:
- Proper ES module support is important for code organization and means that your Microvium modules can also be imported into a node.js or browser environment. You can have the same algorithms shared by your edge devices (microcontrollers), backend servers, and web interfaces, to give your users a unified experience.
- Closures are fundamental to callback-style asynchronous code, as I explained in my previous post.
I’m obviously somewhat biased since Microvium is my own creation, but the overall picture I get is this:
- In this tiny size, Microvium actually supports more core language features than engines more than 5x its size. Some of these features are really useful for writing real-world JS apps.
- Having said that, Microvium has fewer built-in functions — it’s more of a pay-as-go philosophy where your upfront commitment is much less and you bring in support for what you need when you need it.
- The big trade-off is that Microvium doesn’t have a parser at runtime. In the rare case that you really need a parser at runtime, Microvium simply won’t work for you.
Something that made me smile is this note by one of the authors of mJS in a blog posts:
That makes mJS fit into less than 50k of flash space (!) and less than 1k of RAM (!!). That is hard to beat.https://mongoose-os.com/blog/mjs-a-new-approach-to-embedded-scripting/
All of the sizes quoted in this post are when targetting the 32-bit ARM Cortex M0 using GCC with optimization for size. I’m measuring these sizes in June 2022, and of course they may change over time. ↩
ffiin mJS is something that would need to be a built-in in most engines but Microvium’s unique snapshotting approach makes it possible to implement the
ffias a library just like any of the other functions ↩
Please let me know if you know of a smaller JS engine than Microvium. ↩