5 ms·
I write assembly professionally, and I am significantly better at it than any compiler I've used. The considerations you list are all actually pretty easy to ac
by stephencanon 10y ago
I write assembly professionally, and I am significantly better at it than any compiler I've used. The considerations you list are all actually pretty easy to account for, but also mostly not very important for most code sequences on modern architectures(!)
- Most performance-critical code is regular enough to stay far away from the NP-hard corners of register allocation. In the rare instances when I need to do careful hand-allocation to get an inner loop to fit perfectly, the compiler usually gives up and spills several variables.
- Performance-critical code is rarely latency bound, and modern processors are wildly out-of-order, so scheduling based on latency is usually a waste of time (and sometimes a pessimization). Minimizing the critical path length of loop-carried dependencies and balancing port utilization are far more important factors in most code sequences.
When code is latency-bound, it's often due to bad design decisions further up the stack; refactoring those to shift the concern to throughput is vastly more profitable than anything you can do to reduce latency locally, and is the sort of optimization that a compiler simply can't do (and usually can't even tell you you should do). I can tell the guy calling my BLAS library that he should try to batch up many vectors and call GEMM instead of GEMV. The compiler usually doesn't do that, and no-matter how hard it works at optimizing GEMV, it's going to be bound by memory access.
- Encoding length is a factor for whole-program optimization (reducing i-cache pressure), but critical sections often fit into the decoded µop cache / loop buffer / inner cache / etc making this a non-issue. Sometimes you want to choose a particular encoding to optimize for e.g. fetch group alignment or to separate branches to aid the predictor, but there you're usually not trying to minimize the length but to get alignment without no-ops instead (and compilers generally don't do this optimization, because they don't have alignment/instruction size information until really late in the process).
- Avoiding pipeline hazards and getting inlining right are are both worthwhile, but really, really straightforward for the most part. (Inlining is hard to get right if you're trying to avoid wasted work in the name of compiling as fast as you can, but humans don't have the restriction).
The compiler works a lot faster than I do, but I'm a lot better at balancing these and other considerations and methodically optimizing a non-trivial code sequence until it's nearly optimal. I might take a week to optimize the same function that the compiler optimizes in a millisecond, but my code is faster. For most code, the compiler version is fine. For heavily-used system library functions, you definitely want my version instead.
How can we improve compilers so that they're as good as I am? Compilers today mainly work in terms of local transforms on a computation graph, which necessarily cannot capture long-range optimizations that are available to me; most expert assembly programmers I know work mainly in terms of a global resource utilization minimization approach. There's no real reason why we can't make compilers that work that way, but it will almost surely make them significantly slower, so it would be more of a super-optimizer mode that you would only want to run once.
I can also make inferences about what the programmer was "really" trying to do, and go down the hall to ask him. The compiler doesn't (usually) do that; there's a lot of opportunity for compilers to say "hey, I noticed that if x always has this common property, I can make your code much faster -- is that a valid assumption here?"
Compilers aren't bad, but there's a lot of room for improvement, still.
- flamedoge 10y agoyou should just write compilers. I wish processors would remove some features to save power such as OoO and defer it to compiler instruction scheduler.
- stephencanon 10y agoWhile it's not my day-to-day job, I've been an occasional contributor to llvm and clang for almost a decade (though most of my more significant contributions have been in the form of behind-the-scenes conversations with folks who are more regular contributors).
- AstralStorm 10y agoStatic scheduling has been tried in VLIW architectures, such as Itanium. You got both worse assembly too understand, compiler that is hard to write and finally worse performance. Essentially you also lose they advantage of runtime optimization Compare plain java compiler with a java JIT compiler.
- stephencanon 10y agoThat's a bit of an oversimplification; static scheduling is used in extremely low-power architectures (The ARM Cortex-M series, for example), as well as GPUs and DSPs. It has its uses. However, the evidence is pretty compelling that it's not a great fit for "conventional" CPUs doing latency-sensitive tasks. It's a good fit for low-power single-issue architectures and DSPs, because ... well, there's only trivial scheduling to be done because the machine is so dead simple. It's a good fit for GPUs because they are primarily throughput oriented, and can make scheduling issues "go away" by running lots of threads / warps / whatever marketing wants to call them to hide latency.
- flamedoge 10y agoTechnically compiler will have more information about the program than processor will ever have. And vice versa, compiler could not know (or very hard to) more than processor about real time info. If the performance is at steady-state, then I don't see why static insn scheduler wouldn't work given sound methodologies. Old Intel Atoms also used to lack OoO execution.