   Dereferencing faster than local variables?
  I was curious what measurable performance differences there were between different ways of accessing data in C++, so I created several speed tests amortized over many trials.  This is one test that seems very counter-intuitive to me.  It suggests that member variables of a class can be accessed via pointer or reference significantly quicker than local variables (even if I explicitly use the register keyword on the locals).Can anyone see something I did that could be biasing these results, or come up with an explanation for why this difference is showing up?#include <cstdio>#include <ctime>#include <iostream>#include <cmath>struct MyClass{int X;int getX() const{ return X; };};/*MSVC 08 - optimizedsize is 8Local var = 959 picos, -64771072Member var = 918 picos, -64771072Member var by ref= 294 picos, -64771072Member var by getter= 923 picos, -64771072Member var by ptr= 291 picos, -64771072Note - these numbers consistently fluctate by a few picoseconds, but the ref and ptr are consistently about 3 times faster.*/int main(){typedef long long INTTYPE;std::cout << "size is " << sizeof(long long) << "\n";INTTYPE UNIT = 1000000000000;INTTYPE LOOPS = 5000000000;clock_t start, end, diff = 0;register int sum = 0;MyClass c;MyClass *pC = &c//measure time to compute arithmeticint X = 6;start = clock();for(INTTYPE i=0; i<LOOPS; ++i){sum += X;}end = clock();std::cout << "Local var = " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";//measure time to access membersum=0;c.X = 6;start = clock();for(INTTYPE i=0; i<LOOPS; ++i){sum += c.X;}end = clock();std::cout << "Member var = " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";//measure time to access member by refsum=0;c.X = 6;int &rX = c.X;start = clock();for(INTTYPE i=0; i<LOOPS; ++i){sum += rX;}end = clock();std::cout << "Member var by ref= " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";//measure time to access member by gettersum=0;start = clock();for(INTTYPE i=0; i<LOOPS; ++i){sum += c.getX();}end = clock();std::cout << "Member var by getter= " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";//measure time to access member by ptrsum=0;start = clock();for(INTTYPE i=0; i<LOOPS; ++i){sum += pC->X;}end = clock();std::cout << "Member var by ptr= " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";}
  The function calls are probably inlined, so there should be no difference there. The local variable example probably runs out of registers, but I'm not exactly sure why. Look at the assembly.It seems that individual additions take 1 cycle vs. 3 cycles.MSVC would remove the loop altogether, simply computing sum = 6 * LOOPS during compilation.   ;  My Visual C++ 9, compiling 64 bit code, produces the following code for the loops.000000013FF710C3  mov    edx,0FC23AC00hAll times are reported as 0. But now that you've benchmarked how a different ways of adding a constant to a variable 5000000000 times in a loop behaves, you now know which method to use when you want to add a constant to a variable 5000000000 times. Because that is what you just measured.My point is; your test is useless, because it doesn't operate in a realistic environment or scenario.   ;  A more accurate comparison would be made by using a rand() call and adding that into the final product each loop iteration.  You will have the overhead of rand() mucking up your numbers, but it would be a constant change across all tests.  This would force MSVC to keep the loops in the final code.   ;  The loops are not unrolled, I know this because the computation time scales linearly with the number of iterations that I set.  This takes quite a bit of time to calculate since it's amortized over 5 billion iterations.   ;  Whether or not your loops are unrolled doesn't change at least my opinion that your benchmark is useless, because it's not a realistic case you're benchmarking.   ;Quote:Original post by yahastuCan anyone see something I did that could be biasing these results, or come up with an explanation for why this difference is showing up?Yes, there is a simple explanation:You are not testing anything of value.  "Brother Bob" briefly said why your test is useless, but I'll try to go into more detail.You are testing an extremely tight loop with no data loading, no data processing, and no real work.You have no real work in your measurement.  You are essentially trying to measure the time required for a few assembly instructions --- which are well documented by the hardware manufacturer.  Your test completely ignores the fact that you are not the only thing running.  The OS is constantly doing other tasks, switching between thousands of threads and processes.  The overhead involved with a single context switch will completely ruin your measurements.You do not consume or produce anything.  In the real world you will be loading data, processing it, and outputting something.  Loading data pulls from a cache and from main memory and possibly from the disk or other data stores.  Each of these can require thousands of cycles, millions, or if it is pulling from disk, potentially many billions of cycles.Imagine if you had done any real work.  Even as simple as reading data from a data table, performing a math operation on it and storing the results.  Or the simple task of writing a random number to a big array.  The time you spend manipulating the data will be many orders of magnitude bigger than the time spent dereferencing the variables.  Your "benchmark" is useless at anything, except to reveal that you have no idea how real benchmarking works.   ;  Dont fool yourself into thining that the same loop in a different function will produce the same results. Compilers optimise the code in context so the surrounding code can make all the difference. One loop variable might get put into a register and the next might not. Switch the order of the loops and you might get the opposite result. Several of the lines of code that come before or after might affect it, or the fact that the code at the bottom uses a variable from the top.Tests like this really are meaningless, as Brother Bob says. Only time a real world function if you want meaningful results.   ;Quote:Original post by yahastuThe loops are not unrolled, I know this because the computation time scales linearly with the number of iterations that I set.  This takes quite a bit of time to calculate since it's amortized over 5 billion iterations.You can have a loop unrolled and still have linear complexity (linky).  Anyway, the fact that the time does scale linearly tells me  you aren't building the project correctly.  Are you in release mode?  Have you turned on optimizations?  What flags are you using?  What's the generated assembly?  That, and the fact that this benchmark is in an unrealistic, overly simplistic environment makes your results fairly worthless (as has been said).   ;Quote:Original post by yahastuThe loops are not unrolled, I know this because the computation time scales linearly with the number of iterations that I set. huh, well here is an intresting thing.I took the code, created a shiney new project in VS08, and then made an x64 profile based on the win32 one, compiled the code in release and had it output assembler + C++.A segment follows;; 41   : //measure time to compute arithmetic; 42   : int X = 6;; 43   : start = clock();callQWORD PTR __imp_clockmovebx, eax; 44   : for(INTTYPE i=0; i<LOOPS; ++i){; 45   : sum += X;; 46   : }; 47   : end = clock();callQWORD PTR __imp_clock; 48   : std::cout << "Local var = " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";movrbp, 1000000000000; 000000e8d4a51000Hsubeax, ebxmovrdi, 2361183241434822607; 20c49ba5e353f7cfHmovrsi, 5000000000; 000000012a05f200Hmovsxdrcx, eaxmovrax, rdiimulrcx, rbpimulrcxmovrcx, QWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@Asarrdx, 7movrax, rdxshrrax, 63; 0000003fHaddrax, rdxcdqidivrsileardx, OFFSET FLAT:??_C@_0N@OPEPJKNL@Local?5var?5?$DN?5?$AA@movrbx, raxcall??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z ; std::operator<<<std::char_traits<char> >movrdx, rbxmovrcx, raxcallQWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@_J@Zleardx, OFFSET FLAT:??_C@_08GBCCPIGI@?5picos?0?5?$AA@movrcx, raxcall??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z ; std::operator<<<std::char_traits<char> >movedx, -64771072; fffffffffc23ac00Hmovrcx, raxcallQWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@H@Zleardx, OFFSET FLAT:??_C@_01EEMJAFIK@?6?$AA@movrcx, raxcall??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z ; std::operator<<<std::char_traits<char> >I then swapped back to x86, enabled assembly + C++, hit build and got this segment;; 41   : //measure time to compute arithmetic; 42   : int X = 6;; 43   : start = clock();movesi, DWORD PTR __imp__clockaddesp, 8xoredi, edicallesimovebx, eax; 44   : for(INTTYPE i=0; i<LOOPS; ++i){xorecx, ecxxoreax, eaxnpad8$LL15@wmain:; 45   : sum += X;addedi, 6addecx, 1adceax, 0cmpeax, 1jlSHORT $LL15@wmain; 44   : for(INTTYPE i=0; i<LOOPS; ++i){jgSHORT $LN32@wmaincmpecx, 705032704; 2a05f200HjbSHORT $LL15@wmain$LN32@wmain:; 46   : }; 47   : end = clock();callesi; 48   : std::cout << "Local var = " << INTTYPE(end-start)*UNIT/CLOCKS_PER_SEC/LOOPS << " picos, " << sum  << "\n";pushOFFSET ??_C@_01EEMJAFIK@?6?$AA@pushedipushOFFSET ??_C@_08GBCCPIGI@?5picos?0?5?$AA@push232; 000000e8Hsubeax, ebxcdqpush-727379968; d4a51000Hpushedxpusheaxcall__allmulpush0push1000; 000003e8Hpushedxpusheaxcall__alldivpush1push705032704; 2a05f200Hpushedxpusheaxcall__alldivpushedxpusheaxpushecxmovecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@ApushOFFSET ??_C@_0N@OPEPJKNL@Local?5var?5?$DN?5?$AA@pushecxcall??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >addesp, 12; 0000000cHmovecx, eaxcallDWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@_J@Zpusheaxcall??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >addesp, 8movecx, eaxcallDWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Zpusheaxcall??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >addesp, 8Which would confirm that, in x86 mode at least, the loop is not unrolled with default settings.   