Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you reproduce or explain this Visual C++ bug with ctime?

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.

like image 346
Paul Jurczak Avatar asked Oct 04 '14 02:10

Paul Jurczak


1 Answers

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.

like image 94
The Dark Avatar answered Oct 18 '22 19:10

The Dark