Miosix code size optimization

From Miosix Wiki
Revision as of 10:58, 4 May 2014 by Fede.tft (talk | contribs)
Jump to navigation Jump to search

Miosix is designed with the end goal to become an "unix on a chip" written in C++, so it was built with the idea of packing as much standard compliance as possible, without cutting corners. Things like full support to the C and C++ standard libraries, including full support for the C++ STL, thread-safe exception handling, and seldom used features of the C standard libraries, including stdout redirection via

fclose(stdout);
stdout=fopen("/sd/log.txt","w");
puts("writing to a file\n");
fflush(stdout);

are supported. This obviously comes with a code size penalty, especially considering that most features are enabled by default. However, the kernel is very modular, so you don't have to pay the code size cost of unused features in your application.

This guide lists a number of tips to adapt the Miosix kernel to the needs of code-size constrained embedded applications. The exact figures presented here may change as the kernel is developed further, but the tips are expected to remain valid. Considering the stm32f407vg_stm32f4discovery which is a common board used with Miosix, compiling an empty main() with the default settings leads to a code size of over 90KB. With this guide it is possible to reduce code size down to 6KB by removing various features you may not need.

Compile with Os

The default GCC optimization flag used in Miosix is -O2, which generates code optimized for speed. You can however choose -Os which optimizes for size by opening the file miosix/config/Makefile.inc, commenting out the -O2 line, and uncommenting the -Os line, like this:

#OPT_OPTIMIZATION := -O0
#OPT_OPTIMIZATION := -O2
#OPT_OPTIMIZATION := -O3
OPT_OPTIMIZATION := -Os

The code size will usually decrease as a percentage of the total code size, as this option affects how the compiler generates code. 10% decrease is a common figure. Although I haven't found benchmarks showing how much this affects execution speed, usually the impact isn't that high, and code runs definitely much faster with respect to disabling optimizations altogether (with -O0), so if you are tight on code size this is an easy option that does not require to sacrifice any feature of the kernel.

Disable DevFs

Miosix 2.0 comes with an in-memory filesystem, mounted at /dev where device files can be addes, similarly to how a Linux machine works. The main purpose of this is to allow processes to access peripherals, since due to memory protection they can't just access the peripheral registers. However, processes are currently disabled by default because they're still an experimental feature, so you can disable DevFs too. This can be done by commenting out the WITH_DEVFS line in miosix/config/miosix_settings.h, like this:

//#define WITH_DEVFS

TODO: how much size is saved?

Disable the filesystem entirely

Miosix 2.0 has a completely rewritten filesystem subsystem, supporting multiple mountpoints, extensible filesystem types (currently Fat32 and DevFs), unicode UTF8 in file names, directory listing via the standard opendir()/readdir()/closedir() and more POSIX compilance, including inode emulation for Fat32. This comes at a code size cost, though. If your application does not need to read/write from files, you can disable the filesystem subsystem entirely. To do so, open miosix/config/miosix_settings.h and comment out both the WITH_DEVFS and WITH_FILESYSTEM lines. When compared to the default when both filesystem and DevFs are enabled, about 40KB of code size can be saved.

Use iprintf() instead of printf() in your application

iprintf() is a non-standard extension of the newlib C library used by Miosix. It can do all what printf() can do except printing floating point numbers, and thath's why its code size is significantly lower, printing floating point numbers is quite complicated. Miosix already uses iprintf() internally, so to see the code size difference you have to write the following simple code in main()

int main()
{
    int i=0;
    printf("%d\n",i);
    //iprintf("%d\n",i);
}

Trying with iprintf() instead of printf(), a 14KB code size reduction is observed. Note that you have to use a format string containing a % argument, such as %d to see the code size difference. If there is no % in the format string, GCC may replace printf()/iprintf() with a call to puts() as an optimization, and no code size difference can be seen.

Disable boot logs

Maybe you're not using printf() at all in your application, however in this case you're still paying for the code size of iprintf() as the kernel uses it to print boot logs and error logs. To disable them, you can open miosix/config/miosix_settings.h and uncomment WITH_BOOTLOG and WITH_ERRLOG. This allows to save an additional 17KB of code size, but this is only true if there's no printf() call in your code, otherewise the code size saving will be minimal.

Also, keep in mind that error logs are a powerful tool to debug crashes of your application or the kernel during code development, so you may want to disable them only in release builds, and keep them enabled during developemnt.

Finally, note that you can still access the serial port without using printf(), by means of the POSIX calls write() and read(), using STDOUT_FILENO and STDIN_FILENO as the fd argument.

Disable the C++ exception runtime

The Miosix kernel is by default compiled with support for C++ exceptions, and unless you're also enabling processes that depend on C++ exceptions, it is possible to disable their support. To di so, open [Makefile.inc|miosix/config/Makefile.inc]] and uncomment the following line:

OPT_EXCEPT := -fno-exceptions -fno-rtti -D__NO_EXCEPTIONS

This saves nearly 20KB but comes with quite an important side effect, though: if the heap is full, the kernel will reboot the machine. To explain why it is so, consider that the normal behaviour in Miosix to handle a full heap is that the C malloc() returns NULL, while the C++ operator new throws an exception of type bad_alloc. Now, when exceptions are disabled, 'new can no longer throw, but C++ code doesn't check for NULL, as it expects an exception, and that would result in undefined behaviour. The only way to prevent undefined behaviour, is to reboot the machine, and that is what happens.

Putting it all together

With all those tips presented so far applied simultaneously, the code size of an empty main() for the stm32f407vg_stm32f4discovery is reduced to around 10KB.

One more thing, disable the serial port

The kernel, during the boot process, configures a serial port on nearly all the supported boards. If you really have no use for the serial port, you can disable it entirely, saving additional code size. To do so there is no configuration option, but you can edit the board support package file of your board. For the stm32f407vg_stm32f4discovery it's miosix/arch/cortexM4_stm32f4/stm32f407vg_stm32f4discovery/interfaces-impl/bsp.cpp, and comment out the following lines in IRQbspInit()

    DefaultConsole::instance().IRQset(intrusive_ref_ptr<Device>(
        new STM32Serial(defaultSerial,defaultSerialSpeed,
        defaultSerialFlowctrl ? STM32Serial::RTSCTS : STM32Serial::NOFLOWCTRL)));

as well as the following line in shutdown() and reboot()

DefaultConsole::instance().get()->ioctl(IOCTL_SYNC,0);

By applying also this last tip code size can be reduced to around 6KB.