Miosix code size optimization: Difference between revisions
Andreabont (talk | contribs) m Adding Categories |
No edit summary |
||
| Line 1: | Line 1: | ||
Miosix is designed with the | Miosix is designed with the idea to be an "unix on a chip", 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 | ||
<source lang="CPP"> | <source lang="CPP"> | ||
fclose(stdout); | fclose(stdout); | ||
| Line 6: | Line 6: | ||
fflush(stdout); | fflush(stdout); | ||
</source> | </source> | ||
are supported. This obviously comes with a code size penalty, especially considering that | are supported. This obviously comes with a code size penalty, especially considering that some 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. | 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. | 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. In this page we'll use as example the [[stm32f411ce_blackpill]] boardbut the tips presented here are generally applicable. For the application, we'll use an empty ''main()'' to focus on the size of the components of the kernel that are included by default as part of the boot phase: | ||
<source lang="CPP"> | |||
#include <cstdio> | |||
#include <thread> | |||
#include <miosix.h> | |||
#include <interfaces/bsp.h> | |||
using namespace std; | |||
using namespace miosix; | |||
int main() | |||
{ | |||
return 0; | |||
} | |||
</source> | |||
With the default settings and, at the time of writing, Miosix version 3.01, the kernel size is a little over 40KB: 41304 bytes. With this guide it is possible to reduce code size down to 13KB with configuration options alone, and down to 8KB by slimming also the board support package. | |||
Note that optimizations can be applied in any order, there are no dependencies between the steps presented in this page, so you can mix and match to configure a kernel that suits the needs of your application. | |||
== Compile with Os == | == Compile with Os == | ||
The default [http://fedetft.wordpress.com/2009/10/01/gcc-optimization-flags 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 [ | The default [http://fedetft.wordpress.com/2009/10/01/gcc-optimization-flags 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 [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/Makefile.inc config/Makefile.inc], commenting out the ''-O2'' line, and uncommenting the ''-Os'' line, like this: | ||
<source lang="bash"> | <source lang="bash"> | ||
#OPT_OPTIMIZATION | #OPT_OPTIMIZATION ?= -O0 | ||
#OPT_OPTIMIZATION | #OPT_OPTIMIZATION ?= -O2 | ||
#OPT_OPTIMIZATION | #OPT_OPTIMIZATION ?= -O3 | ||
OPT_OPTIMIZATION | OPT_OPTIMIZATION ?= -Os | ||
</source> | </source> | ||
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. | |||
Remember to do a ''make clean; make'' to rebuild the entire kernel for the changes to take effect. | |||
The code size will usually decrease as a percentage of the total code size, as this option affects how the compiler generates code. 5 to 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. | |||
With the empty ''main()'', Miosix v3.10 and the stm32f411ce_blackpill, the code size was reduced to 39368 bytes. | |||
== Disable DevFs == | == Disable DevFs == | ||
Miosix 2 | '''Note:''' starting from Miosix 3, DevFs is disabled by default, so there's nothing to do here. This section is kept for reference. | ||
Miosix 2 comes with an in-memory filesystem, mounted at ''/dev'' where device files can be added, similarly to how a Linux machine works. The main purpose of this is to allow [[Miosix processes|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 [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/miosix_settings.h config/miosix_settings.h], like this: | |||
<source lang="CPP"> | <source lang="CPP"> | ||
| Line 30: | Line 56: | ||
</source> | </source> | ||
== Disable the filesystem entirely == | |||
'''Note:''' starting from Miosix 3, the filesystem is disabled by default since only some board provide the required hardware, so there's nothing to do here. This section is kept for reference. | |||
Miosix 2 | Miosix 2 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 compliance, 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 [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/miosix_settings.h config/miosix_settings.h] and comment out both the ''WITH_DEVFS'' and ''WITH_FILESYSTEM'' lines. | ||
== Use iprintf() instead of printf() in your application == | == Use iprintf() instead of printf() in your application == | ||
''iprintf()'' is a non-standard extension of the [https://www.sourceware.org/newlib newlib] C library used by Miosix. It can do all what ''printf()'' can do except printing floating point numbers, and | '''Note:''' since this example focuses on an empty main, there's nothing to do here, but this tip is worth knowing when writing application code. | ||
''iprintf()'' is a non-standard extension of the [https://www.sourceware.org/newlib newlib] C library used by Miosix. It can do all what ''printf()'' can do except printing floating point numbers, and that'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()'' | |||
<source lang="CPP"> | <source lang="CPP"> | ||
| Line 49: | Line 77: | ||
</source> | </source> | ||
Trying with ''iprintf()'' instead of ''printf()'', a | Trying with ''iprintf()'' instead of ''printf()'', a 18KB 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 the C++ exception runtime == | |||
The Miosix kernel is by default compiled with support for C++ exceptions, and unless you're also enabling [[Miosix processes|processes]] that depend on C++ exceptions, it is possible to disable their support. To do so, open [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/Makefile.inc config/Makefile.inc] and uncomment the following line: | |||
== | <source lang="bash"> | ||
OPT_EXCEPT := -fno-exceptions -fno-rtti -D__NO_EXCEPTIONS | |||
</source> | |||
Remember to do a ''make clean; make'' to rebuild the entire kernel for the changes to take effect. | |||
The code size is reduced to 24384 bytes, but comes with quite an important side effect: if the heap is full, the kernel will reboot the machine. To explain why it is so, consider that the normal behavior 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 inside the C++ standard library doesn't check for ''NULL'', as it expects an exception, and that would result in undefined behavior. The only way to prevent undefined behavior, is to reboot the machine, and that is what Miosix does if you disable exceptions. | |||
== 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 [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/miosix_settings.h config/miosix_settings.h] and comment out the lines ''#define WITH_BOOTLOG'' and ''#define WITH_ERRLOG'': | |||
<source lang="CPP"> | |||
/// \def WITH_BOOTLOG | |||
/// Uncomment to print bootlogs on stdout. | |||
/// By default it is defined (bootlogs are printed) | |||
//#define WITH_BOOTLOG | |||
/// \def WITH_ERRLOG | |||
/// Uncomment for debug information on stdout. | |||
/// By default it is defined (error information is printed) | |||
//#define WITH_ERRLOG | |||
</source> | </source> | ||
This | This allows to save the code size of iprintf, 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 [[Debugging|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 development. | |||
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. | |||
With | With this optimization, the code size is reduced to 13084 bytes. | ||
== One more thing, disable the serial port == | == 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 | 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 ''stm32f411ce_blackpill'' it's [https://github.com/fedetft/miosix-kernel/blob/master/miosix/arch/board/stm32f411ce_blackpill/interfaces-impl/bsp.cpp miosix-kernel/miosix/arch/board/stm32f411ce_blackpill/interfaces-impl/bsp.cpp]. Open the file and comment out the following lines in ''IRQbspInit()'' | ||
<source lang="CPP"> | <source lang="CPP"> | ||
// IRQsetDefaultConsole( | |||
// STM32SerialBase::get<defaultSerialTxPin,defaultSerialRxPin, | |||
defaultSerialFlowctrl | // defaultSerialRtsPin,defaultSerialCtsPin>( | ||
// defaultSerial,defaultSerialSpeed, | |||
// defaultSerialFlowctrl,defaultSerialDma)); | |||
</source> | </source> | ||
| Line 86: | Line 130: | ||
<source lang="CPP"> | <source lang="CPP"> | ||
//ioctl(STDOUT_FILENO,IOCTL_SYNC,0); | |||
</source> | </source> | ||
By applying also this last tip code size can be reduced to | By applying also this last tip code size can be reduced to 8436 bytes. | ||
[[Category:Installation and Configuration]] | [[Category:Installation and Configuration]] | ||
[[Category:Optimization]] | [[Category:Optimization]] | ||
Revision as of 14:15, 10 May 2026
Miosix is designed with the idea to be an "unix on a chip", 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 some 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. In this page we'll use as example the stm32f411ce_blackpill boardbut the tips presented here are generally applicable. For the application, we'll use an empty main() to focus on the size of the components of the kernel that are included by default as part of the boot phase:
#include <cstdio>
#include <thread>
#include <miosix.h>
#include <interfaces/bsp.h>
using namespace std;
using namespace miosix;
int main()
{
return 0;
}
With the default settings and, at the time of writing, Miosix version 3.01, the kernel size is a little over 40KB: 41304 bytes. With this guide it is possible to reduce code size down to 13KB with configuration options alone, and down to 8KB by slimming also the board support package.
Note that optimizations can be applied in any order, there are no dependencies between the steps presented in this page, so you can mix and match to configure a kernel that suits the needs of your application.
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 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
Remember to do a make clean; make to rebuild the entire kernel for the changes to take effect.
The code size will usually decrease as a percentage of the total code size, as this option affects how the compiler generates code. 5 to 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.
With the empty main(), Miosix v3.10 and the stm32f411ce_blackpill, the code size was reduced to 39368 bytes.
Disable DevFs
Note: starting from Miosix 3, DevFs is disabled by default, so there's nothing to do here. This section is kept for reference.
Miosix 2 comes with an in-memory filesystem, mounted at /dev where device files can be added, 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 config/miosix_settings.h, like this:
//#define WITH_DEVFS
Disable the filesystem entirely
Note: starting from Miosix 3, the filesystem is disabled by default since only some board provide the required hardware, so there's nothing to do here. This section is kept for reference.
Miosix 2 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 compliance, 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 config/miosix_settings.h and comment out both the WITH_DEVFS and WITH_FILESYSTEM lines.
Use iprintf() instead of printf() in your application
Note: since this example focuses on an empty main, there's nothing to do here, but this tip is worth knowing when writing application code.
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 that'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 18KB 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 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 do so, open config/Makefile.inc and uncomment the following line:
OPT_EXCEPT := -fno-exceptions -fno-rtti -D__NO_EXCEPTIONS
Remember to do a make clean; make to rebuild the entire kernel for the changes to take effect.
The code size is reduced to 24384 bytes, but comes with quite an important side effect: if the heap is full, the kernel will reboot the machine. To explain why it is so, consider that the normal behavior 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 inside the C++ standard library doesn't check for NULL, as it expects an exception, and that would result in undefined behavior. The only way to prevent undefined behavior, is to reboot the machine, and that is what Miosix does if you disable exceptions.
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 config/miosix_settings.h and comment out the lines #define WITH_BOOTLOG and #define WITH_ERRLOG:
/// \def WITH_BOOTLOG
/// Uncomment to print bootlogs on stdout.
/// By default it is defined (bootlogs are printed)
//#define WITH_BOOTLOG
/// \def WITH_ERRLOG
/// Uncomment for debug information on stdout.
/// By default it is defined (error information is printed)
//#define WITH_ERRLOG
This allows to save the code size of iprintf, 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 development.
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.
With this optimization, the code size is reduced to 13084 bytes.
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 stm32f411ce_blackpill it's miosix-kernel/miosix/arch/board/stm32f411ce_blackpill/interfaces-impl/bsp.cpp. Open the file and comment out the following lines in IRQbspInit()
// IRQsetDefaultConsole(
// STM32SerialBase::get<defaultSerialTxPin,defaultSerialRxPin,
// defaultSerialRtsPin,defaultSerialCtsPin>(
// defaultSerial,defaultSerialSpeed,
// defaultSerialFlowctrl,defaultSerialDma));
as well as the following line in shutdown() and reboot()
//ioctl(STDOUT_FILENO,IOCTL_SYNC,0);
By applying also this last tip code size can be reduced to 8436 bytes.