4 ms·
I think it’s good to understand why compilers emit these. The dominant reason is: it saves registers. X86 is register starved even in 64-bit mode. Just 16 regs
by pizlonator 6y ago
I think it’s good to understand why compilers emit these.
The dominant reason is: it saves registers. X86 is register starved even in 64-bit mode. Just 16 regs means shit gets spilled. If you also needed a tmp reg for each memory access, the way that makes things slower is that it causes spills - usually elsewhere - that wouldn’t have been there if the address was computed by the instruction.
It helps that CPUs make these things fast. But it can be hard to prove that using the addressing mode is faster in the absence of the register pressure scenario I described.
- saagarjha 6y agoWell, if you care about register pressure you’ll only be saving at most one register, since you can just do a shl/add sequence into that to grab the address, and you can save even that if you’re willing to clobber one of your input registers when generating the address, or you want a load into a register from that address later. So I thought that the real reason it existed was that it saved you from the data dependency you’ve just created if you used the technique described above, plus you have that extra lea execution port/low uop unit or whatever that modern Intel processors have these days for doing this. Plus I guess you save a bit on decoding and code size too.
- pizlonator 6y agoOne register is a big deal on x86-64. Even bigger deal on x86-32. I recently wrote a x86-64 backed (as in like 4 years ago) and I recall that if address pattern matching (which is now very complete) was not there, you’d lose >1% perf across the board. I wrote a pretty good x86-32 backend - for a totally different compiler (a whole program PTA-based AOT for JVM bytecode) a long ass time ago and vaguely remember it being like 10-15% there. Note I’m talking about macrobenchmarks in both cases. And not individual ones - the average over many. Also don’t forget those functions that sit on the ledge of caller saves. Having any caller saves is more costly than having none. So if a function is using all of the volatile regs, makes no calls (so no need to promote to caller save), and you cause it to use one more register, then it’s prologue/epilogue slows down. Registers matter a lot. :-) And about your point about freeing up ports or other things: that may be a cool theory, but I’m just saying that it’s hard to show that using those addressing modes is a speedup if register allocation doesn’t care either way (I.e the use of addressing modes doesn’t help spills or prologues). Meaning, most of the reason why compilers emit these is for regalloc, and it is the only reason that I’ve been able to detect as being the one that changes perf across two different back ends.
- pizlonator 6y agoStupid autocorrect of course turned most of my attempts to say callee save with caller save.
- SAI_Peregrinus 6y agoCan't you add "callee" to the autocorrect dictionary? I know I've done that on Android with the AnySoftKeyboard app, not sure about others.
- acqq 6y ago> it can be hard to prove that using the addressing mode is faster in the absence of the register pressure scenario I described. Maybe you personally weren't able to prove to yourself if you did some very small microbenchmarking, but I really believe that measuring the differences of bigger code built with one or another approach it should be relatively straightforward to demonstrate the advantage of using the more compact instructions.
- pizlonator 6y agoWow you are presumptive. These were giant macrobenchmarks. And I said hard, not impossible. As in, most code doesn’t care if you shift or lea, unless it affects regalloc, which it almost always does. It’s true that having smaller code is better regardless of perf - so if perf was neutral we would still use those instructions. The benefit of the smaller instructions for perf is better register allocation, as I said. So, for macrobenchmarks, using the smaller instructions is a win every single time. And that win comes mostly from fewer spills. That’s the point of what I’m saying. I suppose someone could do the experiment of turning on instruction selection patterns for address modes but still “pinning down” a register as if it was needed for the shift-add sequence you would have otherwise emitted. Feel free to do that if you want to prove me wrong. But just hand waving that I must not have run big benchmarks isn’t going to help you learn more about compilers.
- acqq 6y ago> I suppose someone could do the experiment of turning on instruction selection patterns for address modes but still “pinning down” a register as if it was needed for the shift-add sequence you would have otherwise emitted. Starting with that as a thought experiment, isn't it expected that the bigger code can be shown to execute slower, as soon as the performance isn't measured with a microbenchmarking test which avoids to stress caches? Or to be more specific, imagine starting with your proposed modification of the compiler and recompiling both an OS and all the applications. Would the resulting change in the performance be measurable or not? I honestly can't imagine how it wouldn't.
- 6y ago