6

This code example will output time: 0 regardless of the value of N when compiled with Visual Studio Professional 2013 Update 3 in release mode, both 32 and 64-bit option:

#include <iostream>
#include <functional>
#include <ctime>

using namespace std;

void bar(int i, int& x, int& y)  {x = i%13; y = i%23;}

int g(int N = 1E9) {   
  int x, y;
  int r = 0;

  for (int i = 1; i <= N; ++i) {
    bar(i, x, y);
    r += x+y;
  }

  return r;
}

int main()
{
    auto t0 = clock();
    auto r  = g();
    auto t1 = clock();

    cout << r << "  time: " << t1-t0 << endl;

    return 0;
}

When tested with gcc, clang and other version of vc++ on rextester.com, it behaves correctly and outputs time greater than zero. Any clues what is going on here?

I noticed that inlining the g() function restores correct behaviour, but changing declaration and initialization order of t0, r and t1 does not.

Paul Jurczak
  • 7,008
  • 3
  • 47
  • 72
  • 3
    Likely the time to do the operation is below the resolution of the timer Windows is using when calling clock(). I recently wrote an answer here that may give you an idea of using Higher Resolution timers on Windows: http://stackoverflow.com/questions/25954602/ctimespan-always-gets-zero – Michael Petch Oct 04 '14 at 03:03
  • Try N=2147483646 and see if that makes a difference. – Mark Ransom Oct 04 '14 at 03:17
  • @MichaelPetch _time to do the operation is below the resolution of the timer_ - it's not the case, I tried large `N`, where it takes several seconds to execute `f()`. – Paul Jurczak Oct 04 '14 at 03:35
  • I didn't try your code. It was my best guess without loading it in the compiler. The answer to your question is in "The Dark's" post – Michael Petch Oct 04 '14 at 03:36
  • @MarkRansom Tried that. As I mentioned in my question: ` regardless of the value of N`. – Paul Jurczak Oct 04 '14 at 03:36
  • 2
    It is an optimization issue (compiler thinks that calling g has no side effects) so it actually puts both calls to clock before your call to `g`. You should be able to resolve this by changing `auto r = g();` to `volatile auto r = g();` – Michael Petch Oct 04 '14 at 03:41
  • 1
    @MichaelPetch your `volatile` will prevent `g` from moving after the second `clock`, but you also need something to prevent it from moving before the first `clock`, for instance: `volatile n=42; volatile auto r=g(n);`. – Marc Glisse Oct 04 '14 at 17:26
  • @MarcGlisse _you also need something to prevent it from moving before the first clock_ - wouldn't `volatile auto t0 = clock(); volatile auto r = g(); volatile auto t1 = clock();` do the trick? – Paul Jurczak Oct 04 '14 at 19:25
  • 1
    @Paul No, sorry. Think of `g()` as something computing 2+2. It could be computed at compile-time, or at the beginning of main, or between the 2 `clock`, all you know is that it has been computed before being stored in r. To clarify things, a statement like `volatile auto r = g();` doesn't have to stay in one piece. The compiler first splits it into `auto tmp=g(); volatile auto r=tmp;` and the first part can move as early as it likes, since it has no side effect and does not depend on anything. – Marc Glisse Oct 04 '14 at 19:58
  • @MarcGlisse So are you saying that `volatile auto n=42` will prevent call to `g(n)` from moving before `t0 = clock()`, right? – Paul Jurczak Oct 05 '14 at 07:04
  • 1
    @Paul right. Note that this line defining `n` can be before or after the first `clock`, it doesn't matter. Reading the value of `n` is a side effect, so it cannot commute with `clock`. And since `g` depends on that value, `g` must remain after that read. – Marc Glisse Oct 05 '14 at 07:17
  • possible duplicate of [Is it legal for a C++ optimizer to reorder calls to clock()?](http://stackoverflow.com/questions/26190364/is-it-legal-for-a-c-optimizer-to-reorder-calls-to-clock) – Philip Kendall Oct 05 '14 at 21:34
  • @PhilipKendall I'm the author of that question too. This one is about suspected VC++ bug, the other one about C++ language standard. – Paul Jurczak Oct 06 '14 at 02:56

1 Answers1

9

If you look at the disassembly winddow using the debugger you can see the generated code. For VS2012 express in release mode you get this:

00AF1310  push        edi  
    auto t0 = clock();
00AF1311  call        dword ptr ds:[0AF30E0h]  
00AF1317  mov         edi,eax  
    auto r  = g();
    auto t1 = clock();
00AF1319  call        dword ptr ds:[0AF30E0h]  

    cout << r << "  time: " << t1-t0 << endl;
00AF131F  push        dword ptr ds:[0AF3040h]  
00AF1325  sub         eax,edi  
00AF1327  push        eax  
00AF1328  call        g (0AF1270h)  
00AF132D  mov         ecx,dword ptr ds:[0AF3058h]  
00AF1333  push        eax  
00AF1334  call        dword ptr ds:[0AF3030h]  
00AF133A  mov         ecx,eax  
00AF133C  call        std::operator<<<std::char_traits<char> > (0AF17F0h)  
00AF1341  mov         ecx,eax  
00AF1343  call        dword ptr ds:[0AF302Ch]  
00AF1349  mov         ecx,eax  
00AF134B  call        dword ptr ds:[0AF3034h]  

from the first 4 lines of assembly you can see that the two calls to clock (ds:[0AF30E0h]) happen before the call to g. So in this case it doesn't matter how long g takes, the result will only show the time take between those two sequential calls.

It seems VS has determined that g doesn't have any side effects that would affect clock so it is safe to move the calls around.

As Michael Petch points out in the comments, adding volatile to to the declaration of r will stop the compiler from moving the call.

The Dark
  • 8,453
  • 1
  • 16
  • 19
  • 1
    Marking `r` as `volatile` would probably be enough for the compiler to change that behavior. I'd expect this would work `volatile auto r = g();` – Michael Petch Oct 04 '14 at 03:36
  • Is it legal? Following this reasoning, almost all computations don't affect `clock` so it could be moved anywhere by compiler, but it would be a sheer madness. – Paul Jurczak Oct 04 '14 at 03:43
  • @MichaelPetch you are correct - that does it - I'll add it in the answer. – The Dark Oct 04 '14 at 03:43
  • @MichaelPetch You are right. Do you know C++ language rule, which allows to rearrange calls to `g()` and `clock()`? – Paul Jurczak Oct 04 '14 at 03:44
  • The standard doesn't prevent optimizations from doing this however there is a reasonable expectation that what the user observes would be the same with or without optimizations. This is actually more common with global variables shared by multiple threads. – Michael Petch Oct 04 '14 at 03:49
  • Just doing a cursory search on SO I see a similar thing was asked here: http://stackoverflow.com/questions/17328931/at-what-point-is-code-reordering-in-c-optimization-stopped – Michael Petch Oct 04 '14 at 03:52
  • 1
    @MichaelPetch _there is a reasonable expectation that what the user observes would be the same with or without optimizations_ - in fact, there is such a **requirement**. I just looked up _The C++ Programming Language, 4th Edition_ and on page 225 it reads: _A compiler may reorder code to improve performance as long as the result is identical to that of the simple order of execution_ – Paul Jurczak Oct 04 '14 at 04:02
  • 1
    Yes, it would be reasonable to call what you experienced an optimization bug. Volatile keyword exists to give hints to the compiler that such optimizations on variables (values, pointers etc) shouldn't occur. Using optimization comes with these kind of pitfalls. It is usually why most compilers allow you to turn off certain optimizations or set different levels. – Michael Petch Oct 04 '14 at 04:08