How to reduce program size

I'm trying to understand which parts of my sketch affect program size. Defining variable don't seem to affect it, which is surprising. I notice that reducing the text in Serial.print() helps. Now I mostly use Serial.print(F()) to put the strings in EEPROM, but I don't think that reduces the compile size. Including libraries that are not actually used by the code doesn't seem to affect it.

Now I mostly use Serial.print(F()) to put the strings in EEPROM,

PROGMEM not EEPROM.

Code size is affected by lots of things, you can write source, but if you don't call it, the compiler won't include it.

Now I mostly use Serial.print(F()) to put the strings in EEPROM, but I don't think that reduces the compile size.

Of course it doesn't. It actually increases it, as you've now said that constant strings should live in program (code) space, rather than SRAM.

Including libraries that are not actually used by the code doesn't seem to affect it.

Correct. AWOL alludes to why. The linker only writes to the hex file code that actually does stuff - after optimization occurs.

Defining variable don't seem to affect it, which is surprising.

Not if you understand where variables are stored. Program space is read only, unless the bootloader is uploading a new hex file. Since variables need to be written to, they are not stored in program space. So, they do not affect the amount of program space needed.

PaulS: Defining variable don't seem to affect it, which is surprising.

Not if you understand where variables are stored. Program space is read only, unless the bootloader is uploading a new hex file. Since variables need to be written to, they are not stored in program space. So, they do not affect the amount of program space needed.

I have no idea where stuff is stored, or even what the different parts are. Any suggestions on a good source that explains this?

http://arduino.cc/forum/index.php/topic,85196.0/topicseen.html

PaulS: Of course it doesn't. It actually increases it, as you've now said that constant strings should live in program (code) space, rather than SRAM.

Huh? They were in program code space anyway.

A quick test shows that this code:

void setup () 
{
  Serial.begin (115200);
  Serial.println (F("Hello, world."));
}
void loop () {}

... takes 32 bytes more of PROGMEM (program memory) than one without the F macro. However the 32 bytes is fixed. It would be the space taken by the implementation of the routine to pull data out of Flash rather than RAM. So if you add more strings, it is still only 32 bytes larger.

So for a fixed overhead of 32 bytes of program memory, you save substantial amounts of RAM (of which you have a lot less) if you use a lot of strings.

Hi, If you have any global variables that could be made local instead this can save a lot of memory.

Global variables must be read from memory into registers before they can be manipulated, then written back to memory afterwards. If the same variable can be made into a local variable, the compiler has the option of storing it in registers directly. Heres an example -

// Increment a global integer

   nCounter++;
  5e:    80 91 60 00     lds    r24, 0x0060
  62:    90 91 61 00     lds    r25, 0x0061
  66:    01 96           adiw    r24, 0x01    ; 1
  68:    90 93 61 00     sts    0x0061, r25
  6c:    80 93 60 00     sts    0x0060, r24
// Increment a local integer
    nCounter++;
  6e:    2f 5f           subi    r18, 0xFF    ; 255
  70:    3f 4f           sbci    r19, 0xFF    ; 255

Duane B

rcarduino.blogspot.com

DuaneB: If you have any global variables that could be made local instead this can save a lot of memory.

That's good to know.

Eighteen Hints to Reduce Code Size 1. Compile with full size optimization. 2. Use local variables whenever possible. 3. Use the smallest applicable data type. Use unsigned if applicable. 4. If a non-local variable is only referenced within one function, it should be declared static. 5. Collect non-local data in structures whenever natural. This increases the possibility of indirect addressing without pointer reload. 6. Use pointers with offset or declare structures to access memory mapped I/O. 7. Use for(;;) { } for eternal loops. 8. Use do { } while(expression) if applicable. 9. Use descending loop counters and pre-decrement if applicable. 10. Access I/O memory directly (i.e., do not use pointers). 11. Declare main as C_task if not called from anywhere in the program. 12. Use macros instead of functions for tasks that generates less than 2-3 lines assembly code. 13. Reduce the size of the Interrupt Vector segment (INTVEC) to what is actually needed by the application. Alternatively, concatenate all the CODE segments into one declaration and it will be done automatically. 14. Code reuse is intra-modular. Collect several functions in one module (i.e., in one file) to increase code reuse factor. 15. In some cases, full speed optimization results in lower code size than full size optimization. Compile on a module by module basis to investigate what gives the best result. 16. Optimize C_startup to not initialize unused segments (i.e., IDATA0 or IDATA1 if all variables are tiny or small). 17. If possible, avoid calling functions from inside the interrupt routine. 18. Use the smallest possible memory model.

AVR035.

Moderator edit: Smiley corrected.

Hi,

There is another AVR PDF that has previously been posted on these forums, that includes one point missing from the 18 above.

19) If functions are only used within the current file declare them as static, this gives the compiler the option to inline them eliminating all the pushing and popping code.

I tested this and was surpised to see a large function inlined inside loop, the suprising part is that the function is called within two different branches of loop, not sure how the compiler is managing it. I assume its all clever optimisation to ensure that the registers are the same whichever context it is called from (a fist level if and a deeply nested if in the else of the first level if).

Note that loop and setup have thier function prototypes defined elsewhere, but any other functions can be declared static, the compiler can then choose whether to inline them.

Duane B

rcarduino.blogspot.com

I know, it’s an old topic, nevertheless top rated, when I searched for “how to reduce program size”. I hope, my experience can help others searching for this topic.

In my quest, to pack a weather station software on an ATTiny 2313 I made the following experience:

  • Do not use “Serial.print” or anything related to it (~1kB reduction)
  • Do not use Wire.h but for example SoftwareI2CMaster which brings 1.2kB reduction
  • Use Arduino-lite to replace e.g. digitalWrite with the corresponding register commands. I made a header file (see below), which can be used also in current IDE versions. With that, the blink example can be reduced to 396Bytes instead of 830Bytes
  • Do not use external libraries, write the necessary functions directly into your code. This avoids unnecessary if/then clauses and reduces size and C++ overhead.
  • Do not use string operations (~500 Byte reduction), write your own code (example below).

Example for a replacement of i2a:

//Convert integer to char*
//Adapted from https://android.googlesource.com/kernel/lk/+/qcom-dima-8x74-fixes/lib/libc/itoa.c
//Saves 100 bytes compared to itoa or approx. 500 Bytes compared to String.toCharArray()
static char* itoc(unsigned int num)
{
  static unsigned char str[6];
  unsigned int sum = num;
  byte digit;
  for (int i = 4; i >= 0; i--)
  {
    digit = sum % 10;
    str[i] = '0' + digit;
    sum /= 10;
  }
  str[5] = '\0';
  return str;
}

Sorry for reviving such an old thread.

Lite.h (20.2 KB)

You might be interested in Arduino-GPIO and Arduino-TWI for I2C devices. Super small and fast through the use of templates. I'll bet they're even better.