circuitpython/examples/embedding
Jim Mussared b326edf68c all: Remove MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE.
This commit removes all parts of code associated with the existing
MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE optimisation option, including the
-mcache-lookup-bc option to mpy-cross.

This feature originally provided a significant performance boost for Unix,
but wasn't able to be enabled for MCU targets (due to frozen bytecode), and
added significant extra complexity to generating and distributing .mpy
files.

The equivalent performance gain is now provided by the combination of
MICROPY_OPT_LOAD_ATTR_FAST_PATH and MICROPY_OPT_MAP_LOOKUP_CACHE (which has
been enabled on the unix port in the previous commit).

It's hard to provide precise performance numbers, but tests have been run
on a wide variety of architectures (x86-64, ARM Cortex, Aarch64, RISC-V,
xtensa) and they all generally agree on the qualitative improvements seen
by the combination of MICROPY_OPT_LOAD_ATTR_FAST_PATH and
MICROPY_OPT_MAP_LOOKUP_CACHE.

For example, on a "quiet" Linux x64 environment (i3-5010U @ 2.10GHz) the
change from CACHE_MAP_LOOKUP_IN_BYTECODE, to LOAD_ATTR_FAST_PATH combined
with MAP_LOOKUP_CACHE is:

diff of scores (higher is better)
N=2000 M=2000       bccache -> attrmapcache      diff      diff% (error%)
bm_chaos.py        13742.56 ->   13905.67 :   +163.11 =  +1.187% (+/-3.75%)
bm_fannkuch.py        60.13 ->      61.34 :     +1.21 =  +2.012% (+/-2.11%)
bm_fft.py         113083.20 ->  114793.68 :  +1710.48 =  +1.513% (+/-1.57%)
bm_float.py       256552.80 ->  243908.29 : -12644.51 =  -4.929% (+/-1.90%)
bm_hexiom.py         521.93 ->     625.41 :   +103.48 = +19.826% (+/-0.40%)
bm_nqueens.py     197544.25 ->  217713.12 : +20168.87 = +10.210% (+/-3.01%)
bm_pidigits.py      8072.98 ->    8198.75 :   +125.77 =  +1.558% (+/-3.22%)
misc_aes.py        17283.45 ->   16480.52 :   -802.93 =  -4.646% (+/-0.82%)
misc_mandel.py     99083.99 ->  128939.84 : +29855.85 = +30.132% (+/-5.88%)
misc_pystone.py    83860.10 ->   82592.56 :  -1267.54 =  -1.511% (+/-2.27%)
misc_raytrace.py   21490.40 ->   22227.23 :   +736.83 =  +3.429% (+/-1.88%)

This shows that the new optimisations are at least as good as the existing
inline-bytecode-caching, and are sometimes much better (because the new
ones apply caching to a wider variety of map lookups).

The new optimisations can also benefit code generated by the native
emitter, because they apply to the runtime rather than the generated code.
The improvement for the native emitter when LOAD_ATTR_FAST_PATH and
MAP_LOOKUP_CACHE are enabled is (same Linux environment as above):

diff of scores (higher is better)
N=2000 M=2000        native -> nat-attrmapcache  diff      diff% (error%)
bm_chaos.py        14130.62 ->   15464.68 :  +1334.06 =  +9.441% (+/-7.11%)
bm_fannkuch.py        74.96 ->      76.16 :     +1.20 =  +1.601% (+/-1.80%)
bm_fft.py         166682.99 ->  168221.86 :  +1538.87 =  +0.923% (+/-4.20%)
bm_float.py       233415.23 ->  265524.90 : +32109.67 = +13.756% (+/-2.57%)
bm_hexiom.py         628.59 ->     734.17 :   +105.58 = +16.796% (+/-1.39%)
bm_nqueens.py     225418.44 ->  232926.45 :  +7508.01 =  +3.331% (+/-3.10%)
bm_pidigits.py      6322.00 ->    6379.52 :    +57.52 =  +0.910% (+/-5.62%)
misc_aes.py        20670.10 ->   27223.18 :  +6553.08 = +31.703% (+/-1.56%)
misc_mandel.py    138221.11 ->  152014.01 : +13792.90 =  +9.979% (+/-2.46%)
misc_pystone.py    85032.14 ->  105681.44 : +20649.30 = +24.284% (+/-2.25%)
misc_raytrace.py   19800.01 ->   23350.73 :  +3550.72 = +17.933% (+/-2.79%)

In summary, compared to MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE, the new
MICROPY_OPT_LOAD_ATTR_FAST_PATH and MICROPY_OPT_MAP_LOOKUP_CACHE options:
- are simpler;
- take less code size;
- are faster (generally);
- work with code generated by the native emitter;
- can be used on embedded targets with a small and constant RAM overhead;
- allow the same .mpy bytecode to run on all targets.

See #7680 for further discussion.  And see also #7653 for a discussion
about simplifying mpy-cross options.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
2021-09-16 16:04:03 +10:00
..
Makefile examples/embedding: Example for embedding MicroPython in an app. 2016-07-22 22:13:03 +03:00
Makefile.upylib all: Update to point to files in new shared/ directory. 2021-07-12 17:08:10 +10:00
README.md examples/embedding: Add code markup and fix typo in README.md. 2018-06-18 12:29:22 +10:00
hello-embed.c all: Rename BYTES_PER_WORD to MP_BYTES_PER_OBJ_WORD. 2021-02-04 22:46:42 +11:00
mpconfigport.h examples/embedding: Replace symlink of mpconfigport.h with real file. 2019-10-29 22:53:34 +11:00
mpconfigport_minimal.h all: Remove MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE. 2021-09-16 16:04:03 +10:00

README.md

Example of embedding MicroPython in a standalone C application

This directory contains a (very simple!) example of how to embed a MicroPython in an existing C application.

A C application is represented by the file hello-embed.c. It executes a simple Python statement which prints to the standard output.

Building the example

Building the example is as simple as running:

make

It's worth to trace what's happening behind the scenes though:

  1. As a first step, a MicroPython library is built. This is handled by a separate makefile, Makefile.upylib. It is more or less complex, but the good news is that you won't need to change anything in it, just use it as is, the main Makefile shows how. What may require editing though is a MicroPython configuration file. MicroPython is highly configurable, so you would need to build a library suiting your application well, while not bloating its size. Check the options in the file mpconfigport.h. Included is a copy of the "minimal" Unix port, which should be a good start for minimal embedding. For the list of all available options, see py/mpconfig.h.

  2. Once the MicroPython library is built, your application is compiled and linked it. The main Makefile is very simple and shows that the changes you would need to do to your application's Makefile (or other build configuration) are also simple:

a) You would need to use C99 standard (you're using this 15+ years old standard already, not a 25+ years old one, right?).

b) You need to provide a path to MicroPython's top-level dir, for includes.

c) You need to include -DNO_QSTR compile-time flag.

d) Otherwise, just link with the MicroPython library produced in step 1.

Out of tree build

This example is set up to work out of the box, being part of the MicroPython tree. Your application of course will be outside of its tree, but the only thing you need to do is to pass MPTOP variable pointing to MicroPython directory to both Makefiles (in this example, the main Makefile automatically passes it to Makefile.upylib; in your own Makefile, don't forget to use a suitable value).

A practical way to embed MicroPython in your application is to include it as a git submodule. Suppose you included it as libs/micropython. Then in your main Makefile you would have something like:

MPTOP = libs/micropython

my_app: $(MY_OBJS) -lmicropython

-lmicropython:
	$(MAKE) -f $(MPTOP)/examples/embedding/Makefile.upylib MPTOP=$(MPTOP)