Increasing the D Compiler Speed by Over 75%
drdobbs.com
drdobbs.com
It's been a long time since I've used gprof. I switched to Valgrind and OProfile about 10 years ago, and more recently to 'perf' and 'likwid'. If the goal is finding hot-spots, these last might be more convenient since they run with minimal overhead --- a couple percent rather than 100x.
Are there benefits to gprof that I've forgotten?
Are there newer and better profiling tools I don't know about?
...on AMD machines, too? (Not to mention mobile development on ARM.)
This doesn't mean it's worthless, though: Finding your performance bottlenecks on Intel will surely be at least a first-order approximation of the performance on AMD, too.
ARM is a little bit of a different story since the memory model is different, and you might be getting killed by something like unaligned accesses, but that doesn't mean the information is worthless; it just means you should probably use more than one tool. After all, not everything is a nail, but hammers are still good tools.
http://software.intel.com/en-us/non-commercial-software-deve...
If the alternative is something like a handle-based or smart pointer system, then you'll reap the benefits in terms of ease of debugging on a daily basis.
Edit: as aaronblohowiak writes, this method is commonly known as a http://en.wikipedia.org/wiki/Region-based_memory_management
Doesn't Erlang do something like this with processes?
What about doing scratch heaps for data with limited lifetime? Especially if you have a way of somehow reasonably predicting how much heap you're likely going to need (perhaps a heuristics obtained with a bit of machine learning?). Allocate a bit more to give yourself some headroom, use a dumb allocator, and if you run out of space, allocate an emergency area. Obviously, in most cases you won't have to do that, though. (If you're obsessed about speed, you can allocate just blindly and use a memory fault handler to detect that you've run out of space. :-) CLISP does that and it seems to work. Look at GNU libsigsegv.)
Ken Thompson's C compiler does this.
http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cc/comp...
http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cc/lex.... (near the end)
http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cc/macb... (near the end)
Would be interesting to know if a simple bitsshift hash table is faster for a compiler usecase anyway?
- alignment greater than 16-bytes, eg. 128 bytes for isolated buffers.
- the hardware prefetcher like to load cachelines around the memory actually accessed, just in case. So data chunks that will be accessed at the same time better be near each other to save cache usage a bit.
- memory access which does not have a simple pattern is slower than one which is contiguous or have a simple stride.
Plus, every OS, C runtime, and usage pattern is different. YMMV massively depending on everything.
Also, "multiplying by the reciprocal" is a bit simplistic - there's some other instructions added in based on the specific divisor value. Adding more tests and branches for these likely would not pay off.
I only meant to store it in addition because the alternative, storing an index into the list of divisors (and thus reciprocals) might be slower due to an extra indirection.
Note that you may be using an older dmc which did not do the divide optimization.
Templates are stored as ASTs. They are not re-lexed.
If lexing takes relatively smaller times for you, perhaps you have bottlenecks in the later passes?
The top cycle sucker is Lexer::scan(). Here's the source:
https://github.com/D-Programming-Language/dmd/blob/master/sr...
See line 440. It's entirely possible I've missed something glaringly obvious - have a go at it and see if you spot anything.
Oh, and "complaints" is a relative term. DMD is incredibly fast at compiling compared to other compilers - it's just that I want it to go even faster. Anything less than instantaneous I regard as "needs improvement." When D gets a design win at a company, I'll often ask what put D out in front of the competition. "Compile speed" is usually mentioned. Compile speed has a huge effect on programmer productivity.
The solution i've seen was to have each opcode handler determine what the next handler would be and directly jump there.
The whole topic of lexing turning out to be slow is really a thing that should be measured. Really intriguing.
Other tokens probably have similar but less strong "preferences". It's possible that a smart enough branch predictor will learn these on its own, but changing things so that each token ends with its own predicted branch would likely be a win.
Since the branch predictor uses the address of the branch as the 'key', the general goal is to have a separate branch instruction (if, switch) in each place where the probabilities are different enough to favor a different prediction. A wrong prediction costs about 15 cycles, but a correctly predicted branch costs less than 1, so you can put in quite a lot of these and still come out ahead.
Perhaps just ending every token handler with a best guess at what comes next?
if (T->ptr[0] == most_likely_next)
handle_most_likely_next(T);
else scan(T);
I don't have the syntax in my head, by I think you can use 'perf' to record mispredicted indirect branches and then display that sorted by caller.Maybe you could combine the case statements for space and newline, and do a branchless 'cmov' to increment loc.linenum if the match was a newline? This could be combined with loop to grab all the whitespace/newlines in one if you think whitespace is occurs in clumps.
int tmp = real;
if (foo == bar) {
tmp = newval;
}
real = tmp;
I've seen it referred to as a 'hammock'[1], and at least for GCC and ICC it usually is a strong enough hint.[1] http://people.engr.ncsu.edu/ericro/publications/conference_M...
You might eliminate a few per-token function calls and cache thrashing by making it less of a 'pull' tokenizer interface. I.e., instead of the (elegant) method having Lexer::scan(&t) scan and produce just one token, scan a batch of them at once and fill a contiguous array.
Also, your Token class looks like it might be 7 or 8 pointers big. Seems like that could probably slim down a bit.
case '\n':
char* t = p + 1; // skip the nl
while ( *t == ' ' ) // skip the indent spaces
t++; // while avoiding the big switch
p = t;
continue;
I don't know if it's worth, but seems a good compromise.You might see further improvements if you split your allocations between two (or more) allocators. One for memory you expect to remain hot (core to the compiler) and one for stuff you think is one-off. That might improve access locality further.
Granted...since you explicitly stated that your compiler focused on compile speed, I guess optimized code generation isn't your main concern, since the two are more or less mutually exclusive.
I googled a little for a go vs gccgo comparison, but found no general numbers. The situation is similar.