3

Can a C compiler ever optimize a loop by running it?

For example:

int num[] = {1, 2, 3, 4, 5}, i;
for(i = 0; i < sizeof(num)/sizeof(num[0]); i++) {
  if(num[i] > 6) {
    printf("Error in data\n");
    exit(1);
  }
}

Instead of running this each time the program is executed, can the compiler simply run this and optimize it away?

Ariel
  • 25,995
  • 5
  • 59
  • 69
  • 3
    Well, you can check with ex. `gcc -S`. This is called 'loop unrolling'; the compiler effectively copy+pastes the loop body, changing the `i` variable to a constant each time. AFAIK it works well for small loops, and can allow other optimizations to happen (such as eliminating the dead code that the `if` branch would generate). – Colonel Thirty Two Jul 14 '15 at 23:08
  • Try compiling with `-S -funroll-loops` and check the output. "Running" the loop isn't quite the right term, though - it's not like the code is actually executed. – Iskar Jarak Jul 14 '15 at 23:09
  • Look up 'profile-based optimization' or PBO (aka 'profile-guided optimization' or PGO). Yes, it is possible for optimizers to optimize based on the observed performance of the program running. See also 'link-time optimization' (LTO) and 'whole-program optimization'. – Jonathan Leffler Jul 14 '15 at 23:12

4 Answers4

4

Let's have a look… (This really is the only way to tell.)

Fist, I've converted your snippet into something we can actually try to compile and run and saved it in a file named main.c.

#include <stdio.h>

static int
f()
{
  const int num[] = {1, 2, 3, 4, 5};
  int i;
  for (i = 0; i < sizeof(num) / sizeof(num[0]); i++)
    {
      if (num[i] > 6)
        {
          printf("Error in data\n");
          return 1;
        }
    }
  return 0;
}

int
main()
{
  return f();
}

Running gcc -S -O3 main.c produces the following assembly file (in main.s).

        .file   "main.c"
        .section        .text.unlikely,"ax",@progbits
.LCOLDB0:
        .section        .text.startup,"ax",@progbits
.LHOTB0:
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB22:
        .cfi_startproc
        xorl    %eax, %eax
        ret
        .cfi_endproc
.LFE22:
        .size   main, .-main
        .section        .text.unlikely
.LCOLDE0:
        .section        .text.startup
.LHOTE0:
        .ident  "GCC: (GNU) 5.1.0"
        .section        .note.GNU-stack,"",@progbits

Even if you don't know assembly, you'll notice that the string "Error in data\n" is not present in the file so, apparently, some kind of optimization must have taken place.

If we look closer at the machine instructions generated for the main function,

xorl    %eax, %eax
ret

We can see that all it does is XOR'ing the EAX register with itself (which always results in zero) and writing that value into EAX. Then it returns again. The EAX register is used to hold the return value. As we can see, the f function was completely optimized away.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • Interestingly, clang (tested on 3.5.1) can achieve this with a mere `-O1`. – Iskar Jarak Jul 14 '15 at 23:32
  • So say you have a deterministic program that takes a long time to run (perhaps `num` is very long, yet compiled in). At what point does the compiler say "I'll optimize this now", vs "Let it run"? – Ariel Jul 15 '15 at 02:11
  • @Ariel There is no reliable way to tell other than looking at the assembly code actually generated for your program and if you change a tiny bit, something different might happen. At least unless you know intimate details of your compiler. Have a look at [this question](https://stackoverflow.com/q/25878821/1392132) and my answer there for an example how tricky it can become to reason about what optimization the compiler will ultimately decide to perform. – 5gon12eder Jul 15 '15 at 14:34
1

Yes. The C compiler unrolls loops automatically with options -O3 and -Otime.

TheGreatContini
  • 6,429
  • 2
  • 27
  • 37
  • I'm not sure why you need to unroll any loops here. All that's required is to notice that the code does nothing. – Kerrek SB Jul 14 '15 at 23:27
  • Isn't unrolling the loop part of the process that the compiler does to see that the code does nothing? – TheGreatContini Jul 14 '15 at 23:49
  • I don't see why. You can realize easily that the loop body is empty (since the condition is always false) without unrolling... – Kerrek SB Jul 15 '15 at 10:07
  • Yes, I can realise it, but I am not a compiler. How does the compiler realise it? – TheGreatContini Jul 15 '15 at 11:18
  • If even you can realize it, surely it's possible to program a machine to realize the same thing :-) – Kerrek SB Jul 15 '15 at 11:19
  • The point I am trying to get at is how compilers actually do their optimisation, not how you can imagine that they might do it differently! – TheGreatContini Jul 15 '15 at 21:42
  • Well, hundreds of people have spent decades developing the knowledge and sophistication for that, so don't expect the answer in a comment. You can read academic literature on compiler design, or study the implementations of open-source compilers at your heart's content. – Kerrek SB Jul 15 '15 at 22:43
0

You didn't specify the compiler, but using gcc with -O3 and taking the size calculation outside the for maybe it could do a little adjustment.

prmottajr
  • 1,816
  • 1
  • 13
  • 22
0

Compilers can do even better than that. Not only can compilers examine the effect of running code "forward", but the Standard even allows them to work code logic in reverse in situations involving potential Undefined Behavior. For example, given:

#include <stdio.h>
int main(void)
{
  int ch = getchar();
  int q;
  if (ch == 'Z')
    q=5;
  printf("You typed %c and the magic value is %d", ch, q);
  return 0;
}

a compiler would be entitled to assume that the program will never receive any input which would cause the printf to be reached without q having received a value; since the only input character which would cause q to receive a value would be 'Z', a compiler could thus legitimately replace the code with:

int main(void)
{
  getchar();
  printf("You typed Z and the magic value is 5");
}

If the user types Z, the behavior of the original program will be well-defined, and the behavior of the latter will match it. If the user types anything else, the original program will invoke Undefined Behavior and, as a consequence, the Standard will impose no requirements on what the compiler may do. A compiler will be entitled to do anything it likes, including producing the same result as would be produced by typing Z.

supercat
  • 77,689
  • 9
  • 166
  • 211