5

I have lots of Serial.print() and Serial.println() statements throughout my (rather large) program for debugging when things fail. I've commented out the Serial.begin() statement so I don't slow down execution by running a serial port. However, I'd like to leave the Serial.print() calls so I only have to uncomment Serial.begin() later on.

If there is no Serial port active on the device, are the Serial.print() lines still executed at runtime, or does the compiler ignore them? Is there any performance advantage to explicitly commenting out those lines?

  • You may write your program in machine code instead then you'd know exactly what the microcontroller is doing. – qwerty10 Sep 4 '16 at 2:25
  • What has that got to do with Serial prints? – Nick Gammon Sep 4 '16 at 7:31
5

No, there is no USART wasn't enabled so skip all prints. Everything must be done anyways (calls, conversions to strings, writes to UDR - USART Data Register). It is just not sent outside by USART and it doesn't have to wait for UDRE (USART Data Register Empty flag) so it might be slightly faster.

You can use similar technique as Loggers - log level variable and ifs (this can be changed runtime).

Or you can use macros:

// comment out this line, if you want to show logs:
#define NDEBUG

#ifdef NDEBUG
  #define LOG(...)
  #define LOGLN(...)
#else
  #define LOG(...) Serial.print(__VA_ARGS__)
  #define LOGLN(...) Serial.println(__VA_ARGS__)
#endif

And later:

LOG(F("Something: 0x"));
LOGLN(variable, HEX);

And how it looks after macro substitution? For code:

if (...) LOG(F("something")); else LOG(F("something else"));

With NDEBUG defined it's expanded to:

if (...) ; else ;

And without NDEBUG defined it's expanded to:

if (...) Serial.print(F("something")); else Serial.print(F("something else"));
1

The compiler cannot know what the situation with the device is at runtime. The function itself must determine that, so the call will take place, with arguments and everything. A well-written function would test early on whether there was anything to do, and if not then return early - but that still wastes time.

The norm in programming is to have two versions of the code: one labelled "DEBUG", which has lots of print()s, and one labelled "RELEASE", which uses language contructs to ignore them. This is achieved a number of different ways - but Arduino doesn't really support this out of the box.

@KIIV's answer, using #define, is one way to do this. The problem with it comes when you have multiple modules, or are using libraries that themselves do their own print()s. If you only have your main sketch, then it would work fairly well.

1

A quick test shows that not doing a Serial.begin makes things run 10 times as fast. For example:

void setup ()
  {
  Serial.begin (115200);
  pinMode (13, OUTPUT);
  }  // end of setup

void loop ()
  {
  digitalWrite (13, HIGH);
  for (byte i = 0; i < 100; i++)
    Serial.println ("But does it get goat's blood out?");
  digitalWrite (13, LOW);
  delay (500);
  }  // end of loop

That took 292 ms to do 100 x of those prints on my Uno. If I comment out the Serial.begin call it takes 29 ms. So, faster but not incredibly fast.

On my page about debugging I have suggested code to turn on or off debugging prints. The basic idea is to have a Trace define that lets you output stuff if debugging is on, like this:

#define DEBUG false

// conditional debugging
#if DEBUG 

  #define beginDebug()  do { Serial.begin (115200); } while (0)
  #define Trace(x)      Serial.print   (x)
  #define Trace2(x,y)   Serial.print   (x,y)
  #define Traceln(x)    Serial.println (x)
  #define Traceln2(x,y) Serial.println (x,y)
  #define TraceFunc()   do { Serial.print (F("In function: ")); Serial.println (__PRETTY_FUNCTION__); } while (0)


#else
  #define beginDebug()  ((void) 0)
  #define Trace(x)      ((void) 0)
  #define Trace2(x,y)   ((void) 0)
  #define Traceln(x)    ((void) 0)
  #define Traceln2(x,y) ((void) 0)
  #define TraceFunc()   ((void) 0)
#endif // DEBUG


void setup ()
  {
  beginDebug ();
  pinMode (13, OUTPUT);
  }  // end of setup

void loop ()
  {
  digitalWrite (13, HIGH);
  for (byte i = 0; i < 100; i++)
    Traceln ("But does it get goat's blood out?");
  digitalWrite (13, LOW);
  delay (500);
  }  // end of loop

Now in this case, with DEBUG defined as false, the time taken with pin 13 high is now only 5 µs - a substantial speed-up.

If you define DEBUG to be true, then it is much slower - 290 ms as in the first test.


The suggestion made by KIIV will fail if you have if statements:

#ifdef NDEBUG
  #define LOG(...)
  #define LOGLN(...)
#else
  #define LOG(...) Serial.print(__VA_ARGS__);
  #define LOGLN(...) Serial.println(__VA_ARGS__);
#endif

For example:

if (something)
   LOG ("something is true");

digitalWrite (8, HIGH);

Imagine that debugging is off. Now the empty define makes the code look like:

if (something)
  digitalWrite (8, HIGH);

So you have a side-effect that the code behaves differently if debugging is off.


Semicolon is not part of the macro, so it will be expanded into: if (something) ;

See What does the c precompiler do with macros defined as (void)0 - there are subtle issues with using an empty define compared to void(0).

For example:

  Serial.println ("hi there"), LOGLN ("debugging");

That fails to compile (with the empty define) if debugging is off. The void(0) version does compile. Admittedly it's an edge case, but nevertheless ...

  • Semicolon is not part of the macro, so it will be expanded into: if (something) ; – KIIV Sep 4 '16 at 8:50
  • A way around the if(...) [macro] problem is, when debugging is turned off, to define your macros to equate to {}, such as #define LOG(...) {}, then the if(...) turns into if(...) {} – Majenko Sep 4 '16 at 11:33
  • 1
    @Majenko But if you have: #define MACRO() {} and you'll use it as: if (...) MACRO(...); else ... (notice that semicolon) it will turn into if (...) {}; else .... That's why the pattern do { ... } while(0) is used sometimes instead of simple block { ... } – KIIV Sep 4 '16 at 13:20
  • Semicolon is not part of the macro - you edited your post after I made mine. In your original post (as you can see from what I copied/pasted) you had semicolons. – Nick Gammon Sep 4 '16 at 21:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.