title: Why does GCC generate 15-20% faster code if I optimize for size instead of speed?
body: <p>I first noticed in 2009 that GCC (at least on my projects and on my machines) have the tendency to generate noticeably faster code if I optimize for <strong>size</strong> (<code>-Os</code>) instead of speed (<code>-O2</code> or <code>-O3</code>), and I have been wondering ever since why.</p>
  
  <p>I have managed to create (rather silly) code that shows this surprising behavior and is sufficiently small to be posted here.</p>
  
  <pre><code>const int LOOP_BOUND = 200000000;
  
  __attribute__((noinline))
  static int add(const int&amp; x, const int&amp; y) {
      return x + y;
  }
  
  __attribute__((noinline))
  static int work(int xval, int yval) {
      int sum(0);
      for (int i=0; i&lt;LOOP_BOUND; ++i) {
          int x(xval+sum);
          int y(yval+sum);
          int z = add(x, y);
          sum += z;
      }
      return sum;
  }
  
  int main(int , char* argv[]) {
      int result = work(*argv[1], *argv[2]);
      return result;
  }
  </code></pre>
  
  <p>If I compile it with <code>-Os</code>, it takes 0.38&nbsp;s to execute this program, and 0.44&nbsp;s if it is compiled with <code>-O2</code> or <code>-O3</code>. These times are obtained consistently and with practically no noise (gcc 4.7.2, x86_64 GNU/Linux, Intel Core i5-3320M).</p>
  
  <p><em>(Update: I have moved all assembly code to <a href="http://en.wikipedia.org/wiki/GitHub" rel="noreferrer">GitHub</a>: They made the post bloated and apparently add very little value to the questions as the <code>fno-align-*</code> flags have the same effect.)</em></p>
  
  <p>Here is the generated assembly with <a href="https://gist.github.com/baharev/7069582#file-os" rel="noreferrer"><code>-Os</code></a> and <a href="https://gist.github.com/baharev/7069582#file-o2" rel="noreferrer"><code>-O2</code></a>.</p>
  
  <p>Unfortunately, my understanding of assembly is very limited, so I have no idea whether what I did next was correct: I grabbed the assembly for <code>-O2</code> and merged all its differences into the assembly for <code>-Os</code> <em>except</em> the <code>.p2align</code> lines, result <a href="https://gist.github.com/baharev/7069582#file-o2merged" rel="noreferrer">here</a>. This code still runs in 0.38s and <strong>the only difference is the</strong> <code>.p2align</code> <strong>stuff.</strong></p>
  
  <p>If I guess correctly, these are paddings for stack alignment. According to <a href="https://stackoverflow.com/q/7912464/341970">Why does GCC pad functions with NOPs?</a> it is done in the hope that the code will run faster, but apparently this optimization backfired in my case.</p>
  
  <p><strong>Is it the padding that is the culprit in this case? Why and how?</strong></p>
  
  <p>The noise it makes pretty much makes timing micro-optimizations impossible.</p>
  
  <p><strong>How can I make sure that such accidental lucky / unlucky alignments are not interfering when I do micro-optimizations (unrelated to stack alignment) on C or C++ source code?</strong></p>
  
  <hr>
  
  <p><strong>UPDATE:</strong></p>
  
  <p>Following <a href="https://stackoverflow.com/a/19471213/341970">Pascal Cuoq's answer</a> I tinkered a little bit with the alignments. By passing <code>-O2 -fno-align-functions -fno-align-loops</code> to gcc, all <code>.p2align</code> are gone from the assembly and the generated executable runs in 0.38s. According to the <a href="http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html" rel="noreferrer">gcc documentation</a>:</p>
  
  <blockquote>
    <p>-Os enables all -O2 optimizations [but] -Os disables the following optimization flags:</p>
  
  <pre><code>  -falign-functions  -falign-jumps  -falign-loops
    -falign-labels  -freorder-blocks  -freorder-blocks-and-partition
    -fprefetch-loop-arrays
  </code></pre>
  </blockquote>
  
  <p><strong>So, it pretty much seems like a (mis)alignment issue.</strong></p>
  
  <p>I am still skeptical about <code>-march=native</code> as suggested in <a href="https://stackoverflow.com/a/19471823/341970">Marat Dukhan's answer</a>. I am not convinced that it isn't just interfering with this (mis)alignment issue; it has absolutely no effect on my machine. (Nevertheless, I upvoted his answer.)</p>
  
  <hr>
  
  <p><strong>UPDATE 2:</strong></p>
  
  <p><strong>We can take <code>-Os</code> out of the picture.</strong> The following times are obtained by compiling with</p>
  
  <ul>
  <li><p><code>-O2 -fno-omit-frame-pointer</code> 0.37s</p></li>
  <li><p><code>-O2 -fno-align-functions -fno-align-loops</code> 0.37s</p></li>
  <li><p><code>-S -O2</code> then manually moving the assembly of <code>add()</code> after <code>work()</code> 0.37s</p></li>
  <li><p><code>-O2</code> 0.44s</p></li>
  </ul>
  
  <p>It looks like to me the distance of <code>add()</code> from the call site matters a lot. I have tried <code>perf</code>, but the output of <code>perf stat</code> and <code>perf report</code> makes very little sense to me. However, I could only get one consistent result out of it:</p>
  
  <p><code>-O2</code>:</p>
  
  <pre><code> 602,312,864 stalled-cycles-frontend   #    0.00% frontend cycles idle
         3,318 cache-misses
   0.432703993 seconds time elapsed
   [...]
   81.23%  a.out  a.out              [.] work(int, int)
   18.50%  a.out  a.out              [.] add(int const&amp;, int const&amp;) [clone .isra.0]
   [...]
         ¦   __attribute__((noinline))
         ¦   static int add(const int&amp; x, const int&amp; y) {
         ¦       return x + y;
  100.00 ¦     lea    (%rdi,%rsi,1),%eax
         ¦   }
         ¦   ? retq
  [...]
         ¦            int z = add(x, y);
    1.93 ¦    ? callq  add(int const&amp;, int const&amp;) [clone .isra.0]
         ¦            sum += z;
   79.79 ¦      add    %eax,%ebx
  </code></pre>
  
  <p>For <code>fno-align-*</code>:</p>
  
  <pre><code> 604,072,552 stalled-cycles-frontend   #    0.00% frontend cycles idle
         9,508 cache-misses
   0.375681928 seconds time elapsed
   [...]
   82.58%  a.out  a.out              [.] work(int, int)
   16.83%  a.out  a.out              [.] add(int const&amp;, int const&amp;) [clone .isra.0]
   [...]
         ¦   __attribute__((noinline))
         ¦   static int add(const int&amp; x, const int&amp; y) {
         ¦       return x + y;
   51.59 ¦     lea    (%rdi,%rsi,1),%eax
         ¦   }
  [...]
         ¦    __attribute__((noinline))
         ¦    static int work(int xval, int yval) {
         ¦        int sum(0);
         ¦        for (int i=0; i&lt;LOOP_BOUND; ++i) {
         ¦            int x(xval+sum);
    8.20 ¦      lea    0x0(%r13,%rbx,1),%edi
         ¦            int y(yval+sum);
         ¦            int z = add(x, y);
   35.34 ¦    ? callq  add(int const&amp;, int const&amp;) [clone .isra.0]
         ¦            sum += z;
   39.48 ¦      add    %eax,%ebx
         ¦    }
  </code></pre>
  
  <p>For <code>-fno-omit-frame-pointer</code>:</p>
  
  <pre><code> 404,625,639 stalled-cycles-frontend   #    0.00% frontend cycles idle
        10,514 cache-misses
   0.375445137 seconds time elapsed
   [...]
   75.35%  a.out  a.out              [.] add(int const&amp;, int const&amp;) [clone .isra.0]                                                                                     ¦
   24.46%  a.out  a.out              [.] work(int, int)
   [...]
         ¦   __attribute__((noinline))
         ¦   static int add(const int&amp; x, const int&amp; y) {
   18.67 ¦     push   %rbp
         ¦       return x + y;
   18.49 ¦     lea    (%rdi,%rsi,1),%eax
         ¦   const int LOOP_BOUND = 200000000;
         ¦
         ¦   __attribute__((noinline))
         ¦   static int add(const int&amp; x, const int&amp; y) {
         ¦     mov    %rsp,%rbp
         ¦       return x + y;
         ¦   }
   12.71 ¦     pop    %rbp
         ¦   ? retq
   [...]
         ¦            int z = add(x, y);
         ¦    ? callq  add(int const&amp;, int const&amp;) [clone .isra.0]
         ¦            sum += z;
   29.83 ¦      add    %eax,%ebx
  </code></pre>
  
  <p>It looks like we are stalling on the call to <code>add()</code> in the slow case.</p>
  
  <p>I have examined <em>everything</em> that <code>perf -e</code> can spit out on my machine; not just the stats that are given above.</p>
  
  <p>For the same executable, the <code>stalled-cycles-frontend</code> shows linear correlation with the execution time; I did not notice anything else that would correlate so clearly. (Comparing <code>stalled-cycles-frontend</code> for different executables doesn't make sense to me.)</p>
  
  <p>I included the cache misses as it came up as the first comment. I examined all the cache misses that can be measured on my machine by <code>perf</code>, not just the ones given above. The cache misses are very very noisy and show little to no correlation with the execution times.</p>