/Asm-versus-C

Small test of how hand-written assembler compares to C code

Primary LanguageAssemblyMIT LicenseMIT

Asm-versus-C

This repository contains a small test of how hand-written assembler compares to C code.

This is a very simple exercise to address a question that came up in a conversation. I wrote a simple ATmega328p program to toggle two LEDs (one green, one red) wired to pins PB1 and PB2 (Arduino Uno pins 9 and 10, respectively). The pins are toggled at a 1 Hz rate, with the two LEDs on opposite duty cycles (one is on when the other is off).

The 1 Hz rate is driven by an interrupt handler associated with Timer1, CompA configured in CTC mode with a prescalar of 256 and a "top" value of 62,449.

I wrote two versions of this program. The first is written in C (technically C++, but no C++ features are used). The C version is compiled with GCC v7.4.0. The second version is written in assembler (and compiled with gavrasm, but that is much less relevant).

Both work exactly as expected. Their sizes come out as follows:

Version Size (bytes)
C 220
Assembler 186

The C code is 34 bytes larger. Of this, 16 bytes are accounted for by the identity string "GCC: (GNU) 7.4.0" that GCC embeds in the ".text" (i.e, ".cseg") segment of the generated code. We can disregard that (and there is probably some compiler switch available to turn that off, but I haven't checked). The remaining 18 bytes are due to the prologue and epilogue associated with the interrupt handler. The prologue pushes r1, r0, and SREG onto the stack and clears r1. The epilogue restores SREG, r0, and r1. This requires the following instructions:

Count Instruction Size (bytes) Total size
3 push 2 6
1 in 2 2
1 clr 2 2
3 pop 2 6
1 out 1 2
Total 18

So if we disregard the ".ident" string that GCC embeds in the code, the C version is 9.7% larger than the hand-coded assembler, due (in this simple example) entirely to the function prologues and epilogues generated by the compiler to preserve registers and status flags. The compiler does this because it assumes the code in the function may trash these, even though that is not actually the case in this example. The assembler version reflects this understanding and omits the unnecessary saving and restoring.