Optimization guide

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:

  1. Platform endianness
  2. The size of a byte (CHAR_BIT)
  3. The address size of the architecture
  4. 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