Compiler optimizations
When building scrutiny-embedded, make sure to enable compiler optimizations.
- For speed optimization, use
-O2 at least
- For size optimization, use
-Os
The scrutiny-embedded library is written to be portable. Portable code cannot
exploit platform-specific optimizations. For this reason, the library relies on the compiler
to perform these optimizations. The code has been structured to be highly optimizable,
but will have a large footprint if compiled without optimization.
As an example, consider the following piece of code that reads an address given by the server.
uint_least8_t decode_address_big_endian_8bits(unsigned char const *const buf, uintptr_t *const addr){
SCRUTINY_CONSTEXPR unsigned int addr_size = sizeof(void *) / (CHAR_BIT/8);
SCRUTINY_STATIC_ASSERT(addr_size == 1 || addr_size == 2 || addr_size == 4 || addr_size == 8, "Unsupported address size");
uintptr_t computed_addr = 0;
unsigned int i = 0;
if (addr_size >= 8){
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 8) * 56)));
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 8) * 48)));
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 8) * 40)));
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 8) * 32)));
}
if (addr_size >= 4){
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 4) * 24)));
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 4) * 16)));
}
if (addr_size >= 2){
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 2) * 8)));
}
if (addr_size >= 1){
computed_addr |= ((static_cast(buf[i++]) << ((addr_size >= 1) * 0)));
}
*addr = computed_addr;
return static_cast(addr_size);
}
This function takes into account:
- Platform endianness
- The size of a byte (CHAR_BIT)
- The address size of the architecture
- The compiler's ability to evaluate compile-time expressions
Without optimization, this function produces 95 assembly instructions on a x86_64 architecture and 93 instructions
on the ARMv8-A architecture. With -Os or -O1 however, it compiles down to a single bswap instruction
for x86_64 and a single rev instruction on ARMv8-A.
Optional features
Scrutiny provides several compile-time options to disable features not needed by your application.
Enabling only what you need is highly recommended to minimize the library's footprint. You can refer to the instrumentation guide
for an exhaustive list of options.
Datalogging
Although very useful, datalogging may not be required for all applications.
This feature alone accounts for about 50% of the library's ROM and RAM footprint.
If not needed, set SCRUTINY_ENABLE_DATALOGGING=0
Additionally, the number of variables that can be acquired can be controlled via SCRUTINY_DATALOGGING_MAX_SIGNAL=XX.
Each additional signal will take ~8 bytes of RAM on most platforms. Make sure to set this number as low as possible if you are memory-constrained.
64 bits support
Setting SCRUTINY_SUPPORT_64BITS=0 saves a small amount of RAM and ROM. Note that disabling 64-bit support does
not prevent Scrutiny from reading 64-bit variables, since those reads are performed via raw memory access. The only effect is
that 64-bit variables cannot be used as datalogging condition operands or Runtime Published Values.
Protected regions
As explained in the instrumentation guide, special memory regions can be defined to restrict access;
either as read-only or completely forbidden. If no such regions are needed, the enforcement code can be removed entirely,
reducing ROM, RAM, and stack usage. To disable this feature, set SCRUTINY_SUPPORT_PROTECTED_REGIONS=0
Cortex-M4 metrics
As a reference, the memory footprint of the instrumentation library is depicted below for a Cortex-M4 microcontroller.
Build parameters:
- Architecture: Cortex-M4 (ARMv7-M with Thumb2 instruction set)
- Compiler: GCC 13.2 (arm-none-eabi)
- Compile flags:
-Os -fno-rtti -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard
- scrutiny-embedded version: v0.7
Config 1 - All features
SCRUTINY_ENABLE_DATALOGGING=1
SCRUTINY_SUPPORT_64BITS=1
SCRUTINY_SUPPORT_PROTECTED_REGIONS=1
SCRUTINY_DATALOGGING_MAX_SIGNAL=16
Footprint
| Memory type |
Element |
Size |
| ROM |
.text |
11 765 bytes |
| ROM |
.rodata |
89 bytes |
| RAM |
.bss |
4 bytes |
| RAM |
sizeof(MainHandler) |
576 bytes |
| RAM |
sizeof(FixedFrequencyLoopHandler) |
32 bytes |
Worst case stack trace
| MainHandler::process() |
LoopHandler::process() |
[ 24] MainHandler::process
[ 8] MainHandler::process_request
[ 72] MainHandler::process_datalog_control
[ 32] datalogging::DataLogger::configure
[ 32] datalogging::RawFormatEncoder::reset
[ 0] datalogging::RawFormatReader::reset
[ 168] TOTAL |
[ 0] VariableFrequencyLoopHandler::process
[ 16] LoopHandler::process_common
[ 8] datalogging::DataLogger::process
[ 32] datalogging::DataLogger::check_trigger
[ 40] datalogging::fetch_operand
[ 48] MainHandler::fetch_variable_bitfield
[ 0] tools::get_required_type_size_8bits
[ 144] TOTAL
|
Config 2 - Reduced size
SCRUTINY_ENABLE_DATALOGGING=1
SCRUTINY_SUPPORT_64BITS=0
SCRUTINY_SUPPORT_PROTECTED_REGIONS=0
SCRUTINY_DATALOGGING_MAX_SIGNAL=16
Footprint
| Memory type |
Element |
Size |
| ROM |
.text |
10 858 bytes |
| ROM |
.rodata |
89 bytes |
| RAM |
.bss |
4 bytes |
| RAM |
sizeof(MainHandler) |
524 bytes |
| RAM |
sizeof(FixedFrequencyLoopHandler) |
32 bytes |
Worst case stack trace
| MainHandler::process() |
LoopHandler::process() |
[ 24] MainHandler::process
[ 8] MainHandler::process_request
[ 72] MainHandler::process_datalog_control
[ 32] datalogging::DataLogger::configure
[ 32] datalogging::RawFormatEncoder::reset
[ 0] datalogging::RawFormatReader::reset
[ 168] TOTAL |
[ 0] VariableFrequencyLoopHandler::process
[ 16] LoopHandler::process_common
[ 8] datalogging::DataLogger::process
[ 32] datalogging::DataLogger::check_trigger
[ 40] datalogging::fetch_operand
[ 48] MainHandler::fetch_variable_bitfield
[ 0] tools::get_required_type_size_8bits
[ 144] TOTAL
|
Config 3 - No datalogging (minimal)
SCRUTINY_ENABLE_DATALOGGING=0
SCRUTINY_SUPPORT_64BITS=0
SCRUTINY_SUPPORT_PROTECTED_REGIONS=0
Footprint
| Memory type |
Element |
Size |
| ROM |
.text |
5 508 bytes |
| ROM |
.rodata |
89 bytes |
| RAM |
.bss |
4 bytes |
| RAM |
sizeof(MainHandler) |
184 bytes |
| RAM |
sizeof(FixedFrequencyLoopHandler) |
20 bytes |
Worst case stack trace
| MainHandler::process() |
LoopHandler::process() |
[ 24] MainHandler::process
[ 8] MainHandler::process_request
[ 48] MainHandler::process_memory_control
[ 24] protocol::WriteMemoryBlocksRequestParser::init
[ 32] protocol::WriteMemoryBlocksRequestParser::next
[ 0] codecs::decode_address_big_endian_8bits
[ 136] TOTAL |
N/A
|