Go Down

Topic: minimizing codesize by removing println() in favor of print('\n'); (Read 6309 times) previous topic - next topic

robtillaart


Came accross this "optimization". Remove all println() from print.cpp in favor of using \n explicitely in printing.

A simple test - IDE0.22 win 7/64 - shows

Code: [Select]
void setup()
{
  Serial.begin(9600);
  Serial.println("start");
}

void loop(){}

Binary sketch size: 1972 bytes (of a 30720 byte maximum)

Code: [Select]
void setup()
{
  Serial.begin(9600);
  Serial.print("start\n");
}

void loop(){}

Binary sketch size: 1490 bytes (of a 30720 byte maximum)

1972 - 1490 = 482 , that are a lot of \n's
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

nickgammon

Hard to believe they took 482 bytes to output a newline, but there you go ...
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

robtillaart

Time to dive in deeper ...

snippets needed for println() that one string ...

Code: [Select]
void Print::println(const char c[])
{
  print(c);
  println();
}

void Print::print(const char str[]) 
{
  write(str);
}

void Print::println(void)
{
  print('\r');
  print('\n'); 
}

void Print::print(char c, int base)
{
  print((long) c, base);
}

void Print::print(long n, int base)
{
  if (base == 0) {
    write(n);
  } else if (base == 10) {
    if (n < 0) {
      print('-');
      n = -n;
    }
    printNumber(n, 10);
  } else {
    printNumber(n, base);
  }
}


otherwise only this one would be needed

Code: [Select]

void Print::print(const char str[]) 
{
  write(str);
}


if counted correctly
println("start") ==> 6 calls to print(..) and 3 calls to write(..)
print("start\n"); ==> 1 call to print(..) and 1 to write(..)

A q&d performance test shows no difference in speed,

Conclusion: keep in mind when running out of memory.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

PaulS

Quote
Hard to believe they took 482 bytes to output a newline, but there you go ...

It doesn't take 482 bytes to output a newline, but there needs to be an overloaded println() variation for every overloaded print() variation. Together, they add up.
The art of getting good answers lies in asking good questions.

nickgammon

It shouldn't need all those variations, Paul, if it doesn't use them. However I believe I see the problem ...

First for a fair test the "shorter" one should read:

Code: [Select]
void setup()
{
  Serial.begin(9600);
  Serial.print("start\r\n");
}

void loop(){}


That's because println (as you see above) outputs \r and \n.

Code: [Select]
Binary sketch size: 1480 bytes (of a 32256 byte maximum)




Now as for the rest, there is an implementation bug, namely this:

Code: [Select]

void Print::println(void)
{
  print('\r');
  print('\n'); 
}


That should read:

Code: [Select]

void Print::println(void)
{
  write('\r');
  write('\n'); 
}


Why? Because it is calling:

Code: [Select]
void Print::print(char c, int base)

Which casts c to long and then calls:

Code: [Select]
void Print::print(long n, int base)


Which calls:

Code: [Select]
void Print::printNumber(unsigned long n, uint8_t base)

A whole lot of effort (and time) to convert a newline into a decimal number, base 10, and then cast it back to what it was, and then print it.

Compare to this:

Code: [Select]
void myprintln ()
  {
  Serial.write ('\r');
  Serial.write ('\n');
  }

void myprintln (const char c[])
  {
  Serial.print (c);
  myprintln(); 
  }
 
void setup()
{
  Serial.begin(9600);
  myprintln("start");
}

void loop(){}


Code: [Select]
Binary sketch size: 1512 bytes (of a 32256 byte maximum)

Only 32 more bytes now, by using a version of println that writes rather than prints.

So I seriously think the implementors of the Print library should change println to write, not print.

Even better, combine into a single write:

Code: [Select]
void myprintln ()
  {
  Serial.write ("\r\n");
  }

void myprintln (const char c[])
  {
  Serial.print (c);
  myprintln(); 
  }
 
void setup()
{
  Serial.begin(9600);
  myprintln("start");
}

void loop(){}


Now size is:

Code: [Select]
Binary sketch size: 1500 bytes (of a 32256 byte maximum)

Only 20 more bytes. That's not too bad, after all we got another function out of it (println) that we didn't use before.
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

nickgammon

The 1.0 version is much the same:

Code: [Select]
size_t Print::println(void)
{
  size_t n = print('\r');
  n += print('\n');
  return n;
}


That could be:

Code: [Select]
size_t Print::println(void)
{
   return write ("\r\n");
}


Less complex too.
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

robtillaart

#6
Apr 11, 2012, 01:55 pm Last Edit: Apr 11, 2012, 02:01 pm by robtillaart Reason: 1
Hi Nick,

I knew you would dive into it ;)   You came up with the same refactor I saw, write("\r\n");

However it makes no real diff in speed when I used it in Serial as communication overhead is so much more...

For the fair test, you are right. It should be \r\n   however most people will not use the \r as receiving terminals will often add \r automagically.

--- update ---
Reported - http://code.google.com/p/arduino/issues/detail?id=884 -
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Nick,
Would it make sense to put the "\r\n" string in progmem? preserve 3 or 4 bytes of RAM ..
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

pjrc

Teensyduino has a heavily optimized Print (and many other Arduino functions).  Here's the code I wrote for Print::println():

Code: [Select]

size_t Print::println(void)
{
        uint8_t buf[2]={'\r', '\n'};
        return write(buf, 2);
}

nickgammon

Compare:

Code: [Select]
void myprintln ()
  {
  Serial.write ("\r\n");
  }

void myprintln (const char c[])
  {
  Serial.print (c);
  myprintln(); 
  }
 
void setup()
{
  Serial.begin(9600);
  myprintln("start");
}

void loop(){}


Under version 0022, compiles as:

Code: [Select]
Binary sketch size: 1500 bytes (of a 32256 byte maximum)

Under version 1.0, compiles as:

Code: [Select]
Binary sketch size: 3480 bytes (of a 258048 byte maximum)

Forget saving 3 bytes with program memory! Save 1980 bytes by sticking to version 0022! Wow, just wow. That's 6% of memory lost just by upgrading.

However changing myprintln to:

Code: [Select]
void myprintln ()
  {
  Serial.print (F("\r\n"));
  }


Reduces code to:

Code: [Select]
Binary sketch size: 3534 bytes (of a 258048 byte maximum)

So a slight saving, yes.


Teensyduino has a heavily optimized Print (and many other Arduino functions).  Here's the code I wrote for Print::println():

Code: [Select]

size_t Print::println(void)
{
        uint8_t buf[2]={'\r', '\n'};
        return write(buf, 2);
}



Under version 0022 changing myprintln to:

Code: [Select]
void myprintln ()
  {
  uint8_t buf[2]={'\r', '\n'};
  Serial.write(buf, sizeof buf);
  }


Increased code from 1520 to 1528, strangely enough.

Code: [Select]
Binary sketch size: 1528 bytes (of a 32256 byte maximum)
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

pjrc


Compare:

Code: [Select]
Binary sketch size: 1500 bytes (of a 32256 byte maximum)

Under version 1.0, compiles as:

Code: [Select]
Binary sketch size: 3480 bytes (of a 258048 byte maximum)



Yes, but 1.0 also magically increased your maximum size from 32256 to 258048.  That's a pretty impressive upgrade!!!

Or perhaps you tested 0022 on Uno and 1.0 on Mega?  The code sizes between boards aren't really comparable, since lots of extra code gets included to support Mega's 4 serial ports, extra timers and so on.

nickgammon

Lol, you are right. I thought the figure on the right looked a bit strange.

Setting IDE 1.0 to Uno (where I usually have it, to be fair to me) I got:

Code: [Select]
Binary sketch size: 1928 bytes (of a 32256 byte maximum)

Only 428 bytes more for upgrading to version 1.0.

So, here's some advice ... if you are short of memory, stick to 0022 of the IDE. Unfortunately you lose the F() macro then. Ah well, there is always the old-fashioned way of doing program memory. Or you could probably retro-fit the macro.
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

pjrc

Teensyduino is where the F() macro started.... of course also with input from Mikal Hart and Brian Cook, and then it was contributed to Arduino.  Teensyduino supports it on 0022 and 0023.  Even if you're not using Teensy, you could install Teensyduino and copy-n-paste bits from Teensy's Print.cpp to get a F() that works in 0022.

nickgammon

Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

Coding Badly


In addition to the F-macro, I highly recommend Teensyduino for the IDE enhancements.

If you just want to add the F-macro to version 0022, these two diffs should help...

http://code.google.com/p/arduino-tiny/source/diff?spec=svn66&r=58&format=side&path=/trunk/hardware/tiny/cores/tiny/Print.h&old_path=/trunk/hardware/tiny/cores/tiny/Print.h&old=44

http://code.google.com/p/arduino-tiny/source/diff?spec=svn66&r=58&format=side&path=/trunk/hardware/tiny/cores/tiny/Print.cpp&old_path=/trunk/hardware/tiny/cores/tiny/Print.cpp&old=8

Go Up