Code size reduction

I ported’s beer temperature control and monitoring arduino code over to the spark. The headers+cpp code is about 13000 lines, and compiles to 20K bytes on the Uno. (We worked really hard to space-optimize!)

Compiling on the spark, it comes in at 109696 bytes. A no-op app compiles to 74864 bytes, meaning our code comprises 34832 bytes of the total - about 40% bigger, which seems surprising.

My understanding from looking at the linker rules is that the maximum sketch size is 128K-0x5000 which is 110592 bytes, so there’s only a few hundred bytes of free space remaining.

Any tips on how I can look for where the code is larger, or how to reclaim some space?

1 Like

@mdma Our code is pretty heavy and has not been optimized at all for space; we’ve been fast and loose and haven’t yet turned to space optimization. So our code is definitely heavier than it needs to be. As for yours, it’s possible that some of that bloat comes from moving to a 32-bit architecture.

I’m not much of an embedded expert, so I don’t have any great suggestions for how to reduce bloat; hopefully others will have some advice to contribute.

If various built in libraries are not used, do they still compile into the firmware? If so, you can probably get in there and comment some out with a little work.

I believe the linker removes unused sections (I think that’s what -gc-sections does.) And with -ffunction-sections (and -fdata-sections) each function and data item are placed in their own section so they can be garbage collected if they are not referenced.

For example, I have all my app code (as .cpp files) included in application.cpp via #includes. If I comment out the body of the setup and loop methods, the code size drops to the same as an empty application.cpp file. All the code is still presented to the compiler, but linker is removing all of it since it detects it’s not being referenced.

EDIT: here’s an example of the linker pruning unused stuff - we have communications layer, which I had compiled to use the Serial implementation, but there are several other available implementations, such as a No-op one. Switching out the serial for no-op comms reduces the code size by 10K. (While in the arduino Uno the Serial stack takes relatively little space since it’s mostly done in hardware, apart from the leonardo which does it in software.)

@zach - yes size optimizations are turned on (-Os) as this was present in the original makefile.

Its reasonable to expect the same code built for ARM-Thumb2 is a little larger than the code built for AVR8, but not 40-some percent.

Its true that there are a bunch of wiring and spark_wiring classes which, even if never used by application.cpp are still instantiated statically. TwoWire, Serial, Serial1 come to mind, though I’m sure there are others. It would be nice in the future to be able to exclude this by compile time option (or not force the static instance all the time) to reduce the size of the binary.

One thing you can do to get an idea of where your code space utilization is coming from is to use arm-none-eabi-size to give the size of each built .o file. Edit the makefile on line 178 from:

$(SIZE) --format=berkeley $<


$(SIZE) --format=berkeley $(ALLOBJ) $<

and rebuild. You’ll get a listing of the .text and .data utilization of each built .o file in the project. This of course doesn’t help you if you’ve #include’d .cpp files into application.cpp. Instead put your cpp files in core-firmware/src and edit core-firmware/src/ to add the source files to the makefile.The format is pretty self explanatory.

-fdata-sections is not included in CFLAGS. I didn’t include this when I rewrote the makefiles since I was trying to copy the existing options as much as possible. This option is included in the Arduino makefile. I did a quick check and by adding -fdata-sections, the code size of my build went down by about 4%. There may be even more savings to be had if -fdata-sections is added to the makefile for core-communications-lib and core-common-lib. I did not test on hardware that the newly built binaries with this option actually run.

For the curious, change core-firmware/build/makefile:58

CFLAGS += -ffunction-sections -Wall -fmessage-length=0


CFLAGS += -ffunction-sections -fdata-sections -Wall -fmessage-length=0

There’s still plenty of incremental improvements to the build system out there for anyone interested in remainder of the makefile bounty.

1 Like

Thanks for confirming this. I’d been looking at the makefiles, as you say there’s plenty to do there. I may take up the task, especially if there is bounty to be had! :smile:

I didn’t know about the static objects. Compile options for those would be good. I can look into that if everyone else is busy on other things. Although I understand that a a non-cloud version of the firmware will be available at some point, which I imagine would be a fair bit smaller too.

The avr gcc toolchain has a tool nm, which also exists in the arm toolchain. I added this rule to the makefile

%.nm: %.elf
	@echo Invoking: ARM GNU NM
	$(NM)  --demangle --size-sort --print-size -r -td $< > $@

Which lists the symbols in decreasing size - here’s the first handful:

134309804 00005344 T _svfprintf_r
134294352 00004276 t sha1_process
134315744 00003640 T _dtoa_r
536874800 00002000 B spark_protocol
134291200 00001860 T aes_crypt_ecb
134270468 00001452 T aes_decr
134305548 00001336 T _malloc_r
134289226 00001290 T SparkProtocol::event_loop()
536871624 00001064 d impure_data
536872692 00001032 D __malloc_av_
536878564 00001024 B wlan_tx_buffer
536879588 00001024 B wlan_rx_buffer
134328760 00001024 t RT3
134327736 00001024 t RT2
134326712 00001024 t RT1
134325688 00001024 t RT0
134332896 00001024 t FT3
134331872 00001024 t FT2
134330848 00001024 t FT1
134329824 00001024 t FT0
134307576 00000960 T _realloc_r
134303884 00000920 T mpi_exp_mod
134302888 00000912 T mpi_div_mpi
134268320 00000904 T hci_event_handler
134284880 00000732 T Setup0_Process
134299886 00000728 t mpi_mul_hlp

I can use this to compare the sizes of the code on the ARM and on the AVR and see where there is any unexpected discrepancy.


Can someone explain why my binary file size does not change when I have saved about 2K in Flashee code size. Here are the make output file statistics?The elf data file size remains constant at 87512 as does the binary file.

  text    data     bss     dec     hex filename
  6360     124     340    6824    1aa8 obj/src/application.o
  1332       8     360    1700     6a4 obj/src/main.o
   182       4       4     190      be obj/src/newlib_stubs.o
  1994      12    2380    4386    1122 obj/src/spark_utilities.o
  1918     512      48    2478     9ae obj/src/spark_wiring.o
   808       4     204    1016     3f8 obj/src/spark_wiring_eeprom.o
  1204       4     124    1332     534 obj/src/spark_wiring_i2c.o
   420       4      72     496     1f0 obj/src/spark_wiring_interrupts.o
   318       4       8     330     14a obj/src/spark_wiring_ipaddress.o
   478       4      16     498     1f2 obj/src/spark_wiring_network.o
  1030       4       8    1042     412 obj/src/spark_wiring_print.o
   488       4      36     528     210 obj/src/spark_wiring_spi.o
   791       4       8     803     323 obj/src/spark_wiring_stream.o
  2728       0       1    2729     aa9 obj/src/spark_wiring_string.o
  1206       6       8    1220     4c4 obj/src/spark_wiring_tcpclient.o
   698       4       8     710     2c6 obj/src/spark_wiring_tcpserver.o
   824       4      60     888     378 obj/src/spark_wiring_time.o
  1112      68     196    1376     560 obj/src/spark_wiring_usartserial.o
   308       4      24     336     150 obj/src/spark_wiring_usbserial.o
   190       4      12     206      ce obj/src/spark_wiring_wifi.o
  2083      29     156    2268     8dc obj/src/spark_wlan.o
   770       0      12     782     30e obj/src/stm32_it.o
   177      26       0     203      cb obj/src/usb_desc.o
   200       0       4     204      cc obj/src/usb_endp.o
   196      56       4     256     100 obj/src/usb_istr.o
   566     144       1     711     2c7 obj/src/usb_prop.o
  1245       4       8    1257     4e9 obj/src/wifi_credentials_reader.o
  1430       4      32    1466     5ba obj/src/flashee-eeprom.o
   376       0       0     376     178 obj/startup/startup_stm32f10x_md.o
 87512    1408   11680  100600   188f8 core-firmware.elf

I’ve not looked at code size reduction for flashee - would be great if you could post your changes in the flashee thread! I’m kind of surprised you can get any reduction, since the linker should remove any unused code.


I only took out the FAT file system stuff that was referenced in flashee-eeprom.cpp as I am only using your wearlevelling firmware code. I noticed that it reduced the object file size. I am trying to reduce as much overhead as I can so that I can include my own TCP/IP commands in application.cpp. This may not be practicable but desperate measures!

Ok, that explains it. While the total size of the individual object files is reduced, since you’re removing the FATFS stuff, this has no affect on the total linked object size, since the linker removes unused symbols. The linker flag -ffunction-sections makes each function it’s own section - and the linker can track which sections are used (transitively) and will then remove unused sections when building the final executable.

This is why I felt it was safe to add FATFS directly into the library - it would not give any overhead if it was not used. Of course, the long term plan is to use library dependencies when these are available.

thanks for the explanation. Back to plan B!