Deoptimizing a C++ program
stackoverflow.com
stackoverflow.com
However, the question is broad and vague, and doesn't really fit the SO model.
Every rule has exceptions, and judging from the voting clearly a lot of people are ok with allowing this specific exception to the rules.
I just wish the 100 votes were spread out over more days. I answered because I personally thought it was interesting, and I'm not really a rep whore, but still. :P
for (int i=0; i<num_sims; i++) {
gauss_bm[i] = gaussian_box_muller();
}
for (int i=0; i<num_sims; i++) {
S_cur[i] = S_adjust * exp(sqrt(v*v*T)*gauss_bm[i]);
}
for (int i=0; i<num_sims; i++) {
payoff[i] += std::max(S_cur[i] - K, 0.0);
}
for (int i=0; i<num_sims; i++) {
payoff_sum += payoff[i];
}
To make this i7-specific, you have to make sure the arrays are mapped to just the right cache lines, so that writing to an array evicts the dat from the array being read from from the cache (hm, maybe that requires combining some of those loops; what does the level 1 cache of these CPUs look like?)Next step: DRY. Move the for (int i=0; i<num_sims; i++) loop into a function taking a lambda for the loop body. That hides the ugliness of the above code. Here, it probably is fairly easy to accidentally do some extra copying of data.
Next: parallelism. You shouldn't have individual threads process contiguous sections of the arrays; do
for (int i=threadNo; i<num_sims; i+= numThreads) {
instead. And of course, you can multi-thread by having the 4 for loops run in parallel. That, I think, would be the big gain, as it would introduce quite a few data dependencies between threads. It also is in the spirit of the question, I think.It has nothing to do with the i7, but I would also work on worrying about the value of RAND_MAX. It can be as low as 32767. So, if you want to pick a double from the full range of values in the [-1,1] interval you have to make at least 4 calls to rand(). Making the distribution uniform from there will take quite a bit of work, too (there are as many double values between 1/4 and 1/2 as between 1/2 and 1, but you want to pick the former half as often as the latter)
If it's allowed to make own classes, the best thing it can be done is to introduce MyNumber abstract class, then inherit it to make MyDouble, then make NumberFactory and access every double through the virtual operators. Then let every temporary variable be created (with the new operator!) on every loop pass, of course using the factories. Define the operations in a way to need the new temporary MyNumbers too. That way the creations would be hidden in the classes, the calculation would still look relatively innocent.
And additionally, make classes so that they have to do runtime casts for every operation. Make the classes to have multiple inheritance, to make sure figuring out in the runtime which cast works as nontrivial as possible.
I know one commercial program where the programmers actually implemented all this, even more convoluted, and even believed that they "improved" the code with all these insanities.
For style points, use all possible boost classes that you can to achieve the above behavior.
https://macton.smugmug.com/Other/2008-07-15-by-Eye-Fi/n-xmKD...
In the answers, I think Peter covers most of the likely attacks. One that isn't mentioned is "choosing the wrong compiler". Not just compiler options, but compiler. While there isn't a "best" compiler for everything, for a given program often one will perform better than the others. Especially if one is targetting recent Intel CPUs, Intel's compiler can occasionally beat GCC and Clang by surprisingly large amounts. This appears to be one of those times.
Here are the times I see for for this example on a 3.4 GHz Skylake running 64-bit Linux when compiled with different modern compilers:
icpc 16.03: 0.87 s
clang++ 3.8: 1.76 s
g++ 5.3.0: 1.73 s
All were compiled with "-march=native -g -Ofast", which I think causes all of the compilers to use the equivalent of "-ffast-math". At least, I'm pretty sure icpc uses it by default, and I didn't see any difference in performance when I added "-ffast-math" or "-funsafe-math-optimizations" to clang++ and g++. So in this case, I (provocatively) would suggest that using clang++ or g++ would be a subtle 2x deoptimization. There are definitely reasons to prefer them, but for this example that choice looks to come at a large cost in performance.If you want to do your own comparison, Version 17 of the Intel compiler is currently available as a free beta for Windows, Mac, and Linux: https://software.intel.com/en-us/articles/intel-parallel-stu.... I don't know how they are defining the license duration, but when I downloaded yesterday it gave me a license valid until October 2016. Separately, I'd be interested to see what MSVC does with this on a comparable machine.
Still, the general point is good. last time I tried ICC on a heavily vectorized (with intrinsics) program, ICC was a 30% boost over clang or gcc.
The Ubuntu packages I'm using are compiled appear to have been compiled to support AVX, but not AVX2, and hence no FMA. So while GCC and Clang are much slower, this is really an issue of having suboptimal system libraries. The speedup is real, but it's not clear that either GCC or Clang are actually to blame.
When I can get g++ to use Intel's libimf instead of glibc's libm, it's only ~5% slower than icpc. I'd guess much of the remaining difference is function call overhead vs inlining. Clang still lags by another 10%, but I think that's probably because I haven't figured out quite the right incantation to get it to use libimf without also disabling some other useful optimization.
Here are the commands that I ended up with:
clang++ -fno-finite-math-only -march=native -Wall -Wextra -g -O3 option.cc -o option -Wl,-rpath=/opt/intel/compilers_and_libraries/linux/lib/intel64 -L/opt/intel/compilers_and_libraries/linux/lib/intel64 -limf -lintlc
g++ -fno-finite-math-only -march=native -Wall -Wextra -g -Ofast option.cc -o option -Wl,-rpath=/opt/intel/compilers_and_libraries/linux/lib/intel64 -L/opt/intel/compilers_and_libraries/linux/lib/intel64 -limf -lintlc
icpc -march=native -Wall -Wextra -g -Ofast option.cc -o option
The "-fno-finite-math-only" disables an otherwise good clang++ and g++ optimization that requires a __finite_exp() function that libimf does not have. clang++ seems to also need to switch from -Ofast to -O3 to make this stick.
What I don't know yet is whether recompiling glibc (or upgrading to the most recent glibc) will produce better performance out-of-the-box on recent Intel. My searches aren't turning up much information --- anyone know?
gcc and clang will vectorize that, but not exp / log
I don't know how well it's currently working, but it looks like this might have changed recently: https://sourceware.org/glibc/wiki/libmvec
Pre-generating a bunch of random numbers in a linked list would be amusingly evil, though.
For example if it had some actual data structures and operations on those, rather than just arithmetic on a few stack-allocated scalar values, introducing bad cache behavior would be a lot plausible. Another problem is that most of the time is going to be spent in pretty opaque library functions (exp, log, rand) whose execution cost is hard to affect just by rearranging data or operations.
> if it had some actual data structures and operations on those, rather than just arithmetic on a few stack-allocated scalar values
I fully agree, as long as the variables are on the stack and not the part of some structure not much can be done, a good compiler can even just keep them in the registers and inline the functions and remove the dead code. As soon as I'd be allowed to make my own classes and "use C++ features", I know what I'd do, as in my other comment.
I did include several potential microarchitectural slowdowns, like causing a store-forwarding stall by modifying just the high byte of `double` with an XOR to flip the sign bit. That isn't much of a stretch, but will hurt a lot
Multi-threading with a shared atomic loop counter is also really good, and maybe worse than you'd naively expect (i.e. easy to justify re: diabolically incompetent).
I'm now pretty happy with my answer's presentation of some actually relevant ideas that are specific to slowing down an out-of-order pipelined CPU like Haswell. The assignment itself doesn't seem to have much scope for solving it the way it seems to be intended. There's not a lot you can do with those dependency chains that out-of-order execution isn't going to eat for breakfast.
Since the answer has gotten widely publicized, I've tried to include links for further reading on anything people might want to know more about. It's up to 28227 chars in length now, though :P
and yet again StackOveflow do their best to kill a problem/discussion with a lot of interest and interesting comments.
...sigh...
There is almost never only 1 correct answer, in which case a discussion is needed and the answer may require your personal opinion as there may not be a single "best" answer. It would depend on circumstances - more discussion required...
also several days ago there was a joke floating on my fb from a taiwanese programmer about how he gets raises every year by removing multiple loops in his programs to show efficiency improvement.