Will it optimize?
ridiculousfish.com
ridiculousfish.com
> And lastly, this isn’t meant to be a prescriptive post,
> but we all know why micro-optimizing is usually a
> mistake: it wastes your time, it’s easy to screw up
> (see question 5), and it *typically* produces no
> measurable speedup.
I emphasized the typically because this is a key idea: you are fallible -- deal with it!I visited a local hacker-space with a friend of mine to talk about a project of ours. A bunch of those guys are NASA and they know quite a bit about how to engineer for reliability. Since our project involved a lot of soldering, he suggested that we estimate our average solder join failure rate and use that as the number of expected failures to look for. That's what he does in his personal projects.
I exclaimed that was brilliant, then was a little embarrassed. Of course! Duh! I know I'm not perfect and I'm smart enough to have deduced that I have something like an average failure rate for solder joins. So why have I been proceeding for years and years as if I could just do everything perfectly if I just tried hard enough?
Every new function you write is a risk. Minimize your risks. Maximize the cost/benefit. Every routine you optimize is a risk. Minimize your risks. Maximize cost/benefit.
One's problems as a programmer are more likely the result of excess hubris and not insufficient smarts.
EDIT: Fear is as big a problem as hubris. Walk the middle path. You are your own optimization problem, and the optimal solution is almost never a no-brainer shove of a lever all the way in some direction.
That said, if you've ever played World of Warcraft on the Mac, you should worship him as a god.
:-)
His blog is that perfect combination of really meaty articles that are published infrequently enough to really be savoured.
http://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Cor...
Seriously beautiful post layout
Shame about the blog chrome. Seriously all I had above the fold was the fish picture and until I looked at the scrollbar I'd nothing to indicate that the page had any content, I assumed it was some sort of flash game.
Also within the post the code overflows the code boxes for me (I'm 2 clicks up on font size).
Looks to be some problem on the all-posts page too - http://ridiculousfish.com/blog/all-posts/ FWIW.
This reminded me of Linus' blog post on optimizing SHA1 routines in Git [0]. FTA: "Some people seem to think that C is a real programming language, but they are sadly mistaken. It really is about writing almost-portable assembly language, and it turns out that getting good results from SHA1 really is mostly about trying to fight the compilers tendency to try to be clever."
[0] http://torvalds-family.blogspot.com/2009/08/programming.html
http://software.intel.com/en-us/articles/improving-the-perfo...
Ironically, when compiling this C code, you have to use -O, higher optimization levels make the compiler try to be too smart for its own good and actually perform worse.
GCC does this optimization, because strlen is a "built-in function:" one that gcc recognizes and optimizes specially. Disabling built-ins with -fno-builtin defeats this optimization."
No, it does it because strlen is declared to be a pure function, and it is smart enough to realize you are not modifying anything in s. -fno-builtin does not change the generated code, it still calls the strlen function (at least on my gcc), but it is hoisted out of the loop.
I believe it was GCC 3.*, so even older than the one mentioned in the article.
"Did you skip ahead? It's OK. Here's the summary"
I like to think he deliberately did that for hungover-yet-still-interested devs like myself.
And even that I'll sometimes wait to properly comprehend for a second pass.
(s)(s+1)(s+2)...(result)
If `s` has no null bytes until `result`, then `strlen(s)` will terminate on `result`. However, if you change `result` while iterating over `s`, then `result` will be not null and the `strlen()` result will change. Or is there some rule in C standard that says local variables are not in the same memory range as external pointers? Certain pointer operations may result in undefined behavior:
int main()
{
int arr[4] = {0, 1, 2, 3};
int * p = arr + 5; // undefined behavior
}
In short, you can't assume that result will be placed after the array. What if result is optimized to a register? It actually sounds quite likely to me given the tight loop. int main(int argc, char ** argv)
{
int arr[4] = {0, 1, 2, 3};
int * p = arr + 5; // valid
*p; // undefined behavior
return 0;
}"If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined."
That is, if either P or P+N don't point to the same array object, or one past the end of the array object, the result is undefined. Thus,
int arr[4] = {0, 1, 2, 3};
... arr + 4 ... // OK, points to one past the end of the array object
... arr + 5 ... // undefined
I'm sure the C standard is the same.6.5.6.8 from ISO/IEC 9899:201x draft of March 1, 2009:
...If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined....
As for this particular example, result is allocated on the stack and is guaranteed to not lie within s, so the gcc optimization is perfectly safe.
It might seem silly in this example, but as soon as I change the program to a very similar one:
void sum2(const unsigned char *s, unsigned char *result) {
*result = 0;
for (size_t i=0; i < strlen(s); i++) {
*result += s[i];
}
}
It cannot be optimised in the same way anymore (gcc won't) because you can't guarantee memory areas pointed at by arguments don't overlap.This is wrong. Attempting to use random pointers to outside of what your compiler and malloc allocated is undefined.
And indeed, aliasing issues like in your example will prevent gcc from optimizing out excess strlen; it can only do so if it can prove that both global memory and the buffer passed to strlen have not been modified. Local variables and return values cannot alias anything, and part of the guarantee of pure functions like strlen is that they have no side effects.
By any chance, does this sort of information exist at all for Java bytecode compilers? Whenever I write Java code I try to do things the Java way but it usually makes me cringe to think that things like accessing a boolean field requires a function call.
I console myself by thinking that this all gets optimized away by the JIT compiler, but I'd really like to know for sure.
I think the fact that I got the first one wrong made me err too far into thinking GCC was smarter than I had first assumed. And it is, but only for certain things.
Does 'const' mean that the function is not allowed to change the contents of the string, or does it mean that the contents of the string will never change? Suppose the 'sum' function was running in one thread, while in another the string that was passed in as 's' is being continuously written to. While this may not make much sense to do, and the output would not have a reliable value, this optimization would be the wrong thing to do.
But the "what if another thread changes the string" issue is a red herring. The question to keep in mind when optimizing in a multi-threaded context is "does the output of the compiler generated code correspond to at least one possible interleaving of threads". Since this thread doesn't do any synchronization, it's possible that all the function executes without being interrupted by another thread, so this is a sound optimization.
A modern desktop-class microprocessor can have scores of instructions "in flight" at once. Each new instruction that's decoded gets reordered or stalled differently depending on the status of all of those previous instructions and their hardware requirements.
A compiler could try to model the processor's microarchitecture and simulate how it would execute the program, but that simulation would be inaccurate because of data-dependent branching, interrupts, and other unpredictable behaviors. Plus, the microarchitectural details you'd need to create such a simulation are usually not available.
That said, compilers can still tailor code to the microarchitecture in a general way. For example, if you know a processor can only decode one branch instruction per cycle, you can try to reorder the compiled code to avoid back-to-back branches in the instruction stream.
This is true. However, a lot of modern processors do in-order execution, they just aren't made by Intel or AMD for desktops: GPUs, network processors, SIMD DSPs, etc. When I was hacking compilers (6 years ago), compiling for these processors presented difficult challenges and there was a real market for new compiler technology. One project was for a network processor that was not only in-order, but it was not interlocked: correctness depended on the compiler having an exact model of the processor's pipeline. Some registers were not registers, but latches. They got new values every cycle, so if you didn't gab a value on the right cycle, it was gone forever.
For x+x vs x*2 vs x<<1, the trick is to represent them in a uniform way and let the scheduler decide what instruction to emit based.
Yes -- for what it's worth, the compiler I'm working on at the moment has a pre-allocation scheduling pass and a post-allocation scheduling pass, and will also sometimes reschedule during allocation to shorten live ranges and thus ease register pressure.
Moving values between different register sets is INCREDIBLY slow since it involves at least two memory operations.
And faking FP operations with specialized integer code on something with soft-float like ARM might be worth it. I've never done it so I can't really say.
(This does not apply to e.g. auto-vectorization, which replaces 'x[0] = y[0] * z[0]; x[1] = y[1] * z[1]; ...' by a single (SSE?) instructions which calculates all of that at once. Such instructions are much faster and would require dropping down to assembly without a good optimizer.)
Absolutely. Fun to read, made me smarter, but half of it could be different in any other version of gcc.
And frankly, if these are your problems, you're doing pretty well. I worked on a product that had some numerical parts, which were sometimes the limiting factor in performance, though usually not. So one day I was talking to a guy who wrote the numerical code about why regression testing was difficult and subtle, and it was revealed that our numerical results could differ from build to build. I.e., a single build of the product always produced the same results, but if you changed the floating point code, the new build might produce different (though equally accurate) results, even if semantically the program was supposed to perform the same C math operations on the same data.
Which makes perfect sense if you're using the old stack-based x87-style floating point instructions. We couldn't be... not in the 21st century on Opterons... check Makefile and gcc man page... biggest facepalm of my career.
We were able to announce a nice speedup on some workloads in the next release.
I wouldn't be surprised if e.g. MSVC didn't perform that optimization since it doesn't have the pure function attribute.