Constants and Conditionals

If I write into a program lots of conditions based on the value of a constant, so the constant can be seen at the top of the code, to toggle various options, does the compiler recognize the constants will always result in the same conditional branch, and optimize away the rest of the options, or will the processor spend time calculating each conditional branch?

If I understand what you're saying, I would think a switch/case block would make more sense. Perhaps a code snippet would illustrate your question.

If you're disabling sections of your code so that they are never going to be entered, using an #ifdef block or #if with some other conditions will guarantee that the code inside it is completely removed when it's not needed. Preprocessor directives are evaluated before the compiler ever sees the code, so if an #if block's condition evaluated false the code inside of it is effectively deleted and never seen by the compiler, so it will take up no space in the micro.

econjack:
If I understand what you're saying, I would think a switch/case block would make more sense. Perhaps a code snippet would illustrate your question.

My question is not is a switch case better than a series of else if statements.... Here is a example to illustrate my question.

const byte FunctionOption = 0;// 0 = doA(), 1 = doB(), 2 = doC(), 3-255 = doD()
const byte PinOption = 0;// 0 = PD7, 1-255 = PD6

void setup() {
  if (PinOption == 0) {// output on ATmega PD7
    pinMode(7, OUTPUT);
    digitalWrite(7, HIGH);// HIGH = deactivated LOW = active
  }
  else {// output on ATmega PD6
    pinMode(6, OUTPUT);
    digitalWrite(6, HIGH);// HIGH = deactivated LOW = active
  }
  delay(100);
}

void loop() {
  if (FunctionOption == 0) doA();
  else if (FunctionOption == 1) doB();
  else if (FunctionOption == 2) doC();
  else doD();
}

void doA() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(100);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}

void doB() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(200);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}

void doC() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(300);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}

void doD() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(400);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}

My question is, would the compiler optimize this based on the constants to a single procedural process or would the ATmega evaluate all the conditionals each time it runs through the loop?

Jiggy-Ninja:
If you're disabling sections of your code so that they are never going to be entered, using an #ifdef block or #if with some other conditions will guarantee that the code inside it is completely removed when it's not needed. Preprocessor directives are evaluated before the compiler ever sees the code, so if an #if block's condition evaluated false the code inside of it is effectively deleted and never seen by the compiler, so it will take up no space in the micro.

Thanks, Jiggy-Ninja. I can see where #if statements would work for doA(), doB(), doC() or doD(), and I could just as easily set a constant for the delay (so bad example of my question) but a lot of the "options" I have are more like the PinOption in my example, not blocks of code, but a ternary argument here or there in the code, e.g. adding a 1 in a bit mask, iterating over a loop x or y times etc.

Perehama:
Thanks, Jiggy-Ninja. I can see where #if statements would work for doA(), doB(), doC() or doD(), and I could just as easily set a constant for the delay (so bad example of my question) but a lot of the "options" I have are more like the PinOption in my example, not blocks of code, but a ternary argument here or there in the code, e.g. adding a 1 in a bit mask, iterating over a loop x or y times etc.

Then use variable to store the pin and loop numbers. Using a named variable for these simple numbers gives you 3 advantages:

  1. Protection against typos
  2. Single point of reference. If you change what pin you're using, you only need to change the lin where you define the pin variable instead of at every pinMode or digitalWrite you use the number at.
  3. A name gives context to what that value means. What is attached to Pin 6 or 7, what does it control?

I also recommend you do the same thing with the HIGHs and LOWs too, so you don't need to have a comment at every digitalWrite explaining what does what. And if you rewire whatever's attached to that pin to invert the logic, you just need to redefine the variables that represent the HIGH and LOW values instead of at every digitalWrite statement.

Which of these two do you think is better?

pinMode( 6, OUTPUT );
digitalWrite( 6, HIGH ); // the hell's this crap?
const byte RELAY_PIN = 6;
const byte RELAY_ON = LOW;
const byte RELAY_OFF = RELAY_ON==LOW? HIGH:LOW; //invert value of RELAY_ON

pinMode( RELAY_PIN, OUTPUT );
digitalWrite( RELAY_PIN, RELAY_OFF ); // do I really need to tell you what this does?

does the compiler recognize the constants will always result in the same conditional branch, and optimize away the rest of the options, or will the processor spend time calculating each conditional branch?

I think that it is past time that you learned to look at the intermediate files that are, or could be, produced during the compiling/linking process. You really shouldn't have to ask this question.

A very simple way to investigate questions like this is to look at the sketch size reported by the Arduino IDE. I know you're thinking about execution time, but there is often a good correlation between the two. Those operations that take time also take space. So compile your code:

Sketch uses 932 bytes (2%) of program storage space. Maximum is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.

then comment out the code you think might be taking up space:

const byte FunctionOption = 0;// 0 = doA(), 1 = doB(), 2 = doC(), 3-255 = doD()
const byte PinOption = 0;// 0 = PD7, 1-255 = PD6

void setup() {
  //if (PinOption == 0) {// output on ATmega PD7
    pinMode(7, OUTPUT);
    digitalWrite(7, HIGH);// HIGH = deactivated LOW = active
  //}
  /*else {// output on ATmega PD6
    pinMode(6, OUTPUT);
    digitalWrite(6, HIGH);// HIGH = deactivated LOW = active
  }*/
  delay(100);
}

void loop() {
  /*if (FunctionOption == 0)*/ doA();
  /*else if (FunctionOption == 1) doB();
  else if (FunctionOption == 2) doC();
  else doD();*/
}

void doA() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(100);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}
/*
void doB() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(200);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}

void doC() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(300);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}

void doD() {
  digitalWrite((PinOption == 0) ? 7 : 6, LOW);
  delay(400);
  digitalWrite((PinOption == 0) ? 7 : 6, HIGH);
  delay(100);
}*/

and compile again:

Sketch uses 932 bytes (2%) of program storage space. Maximum is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.

Generally, the compiler is very smart about optimizing the code. It gets better over time, so you might even get different results depending on which compiler version you're using.

I would avoid using the preprocessor directives if possible. Although it leaves no questions about optimization, it also makes the code much more difficult to understand and troubleshoot. There are things you can't do any other way than the preprocessor, but it's best to avoid it when it's not absolutely necessary. On the other hand, it can be nice when investigating questions like this to add some preprocessor conditionals you can use to switch on and off parts of the code for comparisons, rather than wasting time commenting a bunch of different sections of code. I would only use that for experimentation and research though. I wouldn't leave it in my finished code.

On the other hand, it can be nice when investigating questions like this to add some preprocessor conditionals you can use to switch on and off parts of the code for comparisons, rather than wasting time commenting a bunch of different sections of code.

One of the few things I like about Visual Studio is the ability to select a block of code, and press Ctrl-K, Ctrl-C, and have each line in the block commented out. Select the same block, and Ctrl-K, Ctrl-U uncomments it.

The Arduino IDE does have that feature. It's Ctrl-/ or Edit > Comment/Uncomment. That's still a bit inconvenient when there are multiple separate sections of code you want to enable/disable repeatedly.

Perehama:
does the compiler recognize the constants will always result in the same conditional branch, and optimize away the rest of the options, or will the processor spend time calculating each conditional branch?

I suspect that you could test that by compiling two different versions of the code. In one case use constants and in the other case define the items as volatile variables which the compiler will not (AFAIK) optimize away.

..R