Earlier quoted context omitted.
I think you two are arguing past each other. Calling conventions are obviously needed for syscalls and dynamically linked libraries. I don't think anyone is denying that. But most function calls aren't made to shared code. Almost all function calls are made to private functions, which exist within a binary, and are under the control of the compiler. If I steelman the person you're arguing with, I think what they're c…
You get it. That's exactly what I'm referring to. But - moving values between registers (or between a register and the stack) is crazy fast anyway. I'd love to see some benchmarks showing how much of a difference this optimisation would make in practice. It's fast, but the instructions still occupy space in caches, memory, and decoders; space that could've been used for other more valuable instructions. The problem i…
Don't Clobber the Frame Pointer
41–46 of 46 posts
Re: Don't Clobber the Frame Pointer
#42Speaking as an Asm programmer for several decades: Calling conventions are stupid. They are the results of mindless stupid-compiler-oriented thinking from a time when compilers produced horrible copy-paste-replace code. The CPU itself couldn't care less which registers you use for what. So many wasted bytes on moving values between registers, just because the calling convention wanted it there, and no other reason. T…
Re: Don't Clobber the Frame Pointer
#43Earlier quoted context omitted.
Go isn't built on an existing compiler framework like LLVM. It does its own code generation, has its own assembler.
There is an accident of history here. Go was developed with the plan 9 C compiler suite as a starting point. Most notably those compilers did not generate assembler -- they emitted object code directly. This is described here: https://9p.io/sys/doc/compiler.html . The assembler facilitated transforming hand-written assembly to object code. And here the plan 9 folks chose a new syntax, probably because it was simpler…
Re: Don't Clobber the Frame Pointer
#44Earlier quoted context omitted.
I think you two are arguing past each other. Calling conventions are obviously needed for syscalls and dynamically linked libraries. I don't think anyone is denying that. But most function calls aren't made to shared code. Almost all function calls are made to private functions, which exist within a binary, and are under the control of the compiler. If I steelman the person you're arguing with, I think what they're c…
> Almost all function calls are made to private functions, which exist within a binary, and are under the control of the compiler Do you understand that those private functions are often called from multiple places in your private binary? Do you expect the compiler to emit different prologues at each call site? And even if it did, one side (callee) is fixed right? Do you expect the compiler to find the globally optim…
Yes, I understand all that. But this optimisation is clearly possible to write. On the face of it, it sounds similar to the register allocation problem within a function, but it needs to make decisions globally. Rewriting each caller's code would be easy. The hard part would be deciding which registers to use for each emitted function's parameters. You'd have to look at all of the callers to figure out the best choices. And each choice affects all the other choices.
So yes, it would be a very, very difficult optimisation to implement. Especially given how modern compilers are architected, using code units and a linker. The compiler needs to look at the functions "all at once".
Inlining isn't the same. But I agree that you'd get the most benefit from this optimisation in small, hot functions. If those functions are already being inlined, all of the benefit of this optimisation would disappear.
> The reality is that neither compilers nor compiler engineers are dumb and if you (one) has a bright idea that hasn't been implemented widely then it's highly likely someone has already considered it and there are flaws.
Compiler engineers are smart. But they were also smart 20 years ago, and its clear in hindsight that the compilers of the time left a lot of performance on the table. I bet we're still leaving a lot of performance on the table.
The question in my mind is simply, would the juice be worth the squeeze? It sounds like we agree that it would be a very difficult optimisation to implement. I think the commenter above is right that it would make programs smaller. The remaining question is, would it make them significantly faster? Would it make programs faster enough to justify the implementation complexity?
I personally doubt it. Look at Rust. Rust binaries are often much bigger than their C equivalents because of bounds checks and various other runtime checks. But in my experience, the bloated binary sizes don't seem to make much of a performance difference. If anything, rust code is often (somehow) slightly faster than the equivalent C when I've measured it. (And this was true even before noalias was enabled.)
But it would be a cool thing to try out. Godspeed to anyone interested in giving it a go.
Re: Don't Clobber the Frame Pointer
#45Re: Don't Clobber the Frame Pointer
#46Earlier quoted context omitted.
Depending on how you count, the ratio might not be that small. A lot of hot code are written in hand-coded inline assembly, so in terms of CPU cycles run it's probably non-negligible. i.e. take a look at the glibc implementation of 'strcmp` [0] [0] https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/m...
> A lot of hot code are written in hand-coded inline assembly I know... I write GPU assembly for a living... And still I make that wager. It's not a lot. It's not even a little. It's an epsilon (overall). And it gets smaller over time.
If small hot loops tend to be disproportionately hand written, and certain programs spend the majority of time in their hot loops, that could still be a decent percentage of time/instructions executed.