Microvium is very small
*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). In the TLDR headline, I’ve rounded this up again to “less than 16 kB” since that figure will remain true for a very long time (possibly forever). Similarly, the idle RAM usage for a VM can be as low as 22 B1, but I’ve rounded this up to “less than 64 bytes” to accommodate different situations and future expansion.
Does size matter?
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.
How does it compare?
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 62 bytes3. 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|
Comparing with mJS
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 you4.
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/
When compiled for a 16-bit device and excluding any built-in objects. ↩
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. ↩
This idle memory figure includes the overhead of a single heap bucket with the default array prototype. The earlier quoted “kernel state” does not include this but includes the register bank which is not allocated during idle 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. ↩
5 Replies to “Microvium is very small”
This is awesome! About 15 years ago I tried to get a scripting language built into WD HDDs, mainly to allow the same code to be used for remote and built-in tests, with a suitable abstraction for the hardware layer. The idea was to debug using a desktop connection, then move the script to the drive.
Management had a hard time taking the project seriously when I suggested porting Pawn – you can imagine what they heard when I said that with my English accent!
Yes, this sounds very similar to my situation. At every past job where I’ve worked as a firmware engineer, we’ve eventually reached a point where it would make things a lot cleaner if we had scriptable behavior that was shared between the embedded device and say a backend server or test environment. A concrete example is that you can pay for parking at a parking meter (which has a microcontroller) or pay for the same parking space on your phone, so you want the logic, user workflow, and tariff rules to be the same either way. If Microvium had existed when I was doing that job, I would definitely have used it. I kept running into similar problems, so I decided to create Microvium.
I think it’s pretty easy to make calls from JS into C. Have a look at the C code in the getting-started guide for an example where the JS calls
printthat is implemented in C. Let me know if anything is confusing so I can revise the guide if necessary, since one of the objectives of Microvium is to be easy to use.
Nice post thanks !!!
tell me more…I’m a freak of arduino/raspberry how can I use it on SBC running on arm 64?
My suggestion is to try the getting-started guide linked from the readme
Let me know how it goes!