Now any load from the L3 cache memory or from the main memory takes much more time than any other instruction (not counting exceptions generated by instructions, which include many memory accesses that slow them down, or deprecated instructions that are kept for backwards compatibility and that are executed by long microcode sequences).
Paste in assembly code, check "Trace Table" and run, then "Open Trace". Not sure if it will help with your annoying colleague, but it gives a much more concrete idea about how a processor will execute any given code.
Or, if you want to channel their energy into something slightly more direct, there's also https://quick-bench.com/ which allows easy micro-benchmarking. Still not guaranteed to be relevant to any real-world scenario, but more data-driven than "vibes".
> I have a colleague who has really no idea what he's talking about with respect to machine performance, and who did not have the requisite knowledge of how to peep at the assembly code of a given function with the standard tools like objdump, who now loves to send everyone godbolt links in slack, along with his suppositions about which function will be faster, based entirely on vibes (mostly, instruction count).
This, however, just no.
Instruction counts are only useful if everything is guaranteed to be in registers.
It was a completely unnecessary instruction from a correctness perspective, because it had no effect on the answer. However, it was important for performance; removing the instruction made the calculation slower.
It would be fun to non-destructively randomize the instruction stream and have an ML model learn how to remove hazards.
I do totally get how some people learn just enough to be annoying. Generally I still think that's not a good reason to gatekeep them.
Write program -> compile -> disassemble w/ some mapping -> make notes -> repeat.
Eventually your brains pattern recognition starts to allow you to do neat things with disassembles of programs without source code.
push rbp
this is going to take the contents of rbp and push it onto the top of the stack - this will probably also change the stack pointer mov rbp, rsp
move goes left <-, like a = 5, not 5 = a.
so, copy the updated stack pointer into rbp mov DWORD PTR [rbp-4], edi
now, I'm not 100% sure, but I believe this guy puts edi just under the value we pushed to the top of the stack mov eax, DWORD PTR [rbp-4]
Take that value, and put it into eax, I'm not 100% sure why it's not just mov eax edi. imul eax, eax
integer multiply, this is the part that does the double. pop rbp
restore rbp (which we messed with) ret
and we're done.there are at least three holes in my understanding - but those three are not _that_ hard to track down.
1, does the stack pointer actually auto increment? (I think it does) 2, imul overflow and setting sign flags and such. - that shouldn't be hard to run down.
3, what is the c calling convention? it looks like the argument is top of stack, but also in edi - is that shuffling really needed? I think there's a bucket of implicit behavior there that's kinda scary.
I would _hope_ unless linking to a library, whatever called this, just did the imul eax eax.
My understanding may be deeply flawed, but explaining my assumptions and my understanding does two things.
1, it helps me learn.
2, it helps others re-evaluate their assumptions and possibly see from a different viewpoint.
I'm not saying spam compiler lists. But a clear and well thought out question can certainly advance discussion. It forces people to formalize their assumptions.
With -O3, the code is:
imul edi, edi
mov eax, edi
ret
Yep, the calling convention for x86-64 on Linux and macOS passes the first six integer arguments in rdi, rsi, rdx, rcx, r8, and r9, and then spills to stack.Having originally learned the basics of assembly on the chronically register-deprived x86, it took me a while to get used to the fact that standard CCs now pass things in registers (and rsi and rdi in particular, retaining their ancient names while being completely general-purpose these days).
notice also there is a 128-byte space ("red zone") before %rsp that keeps its contents between function calls but preserved by OS during interrupts. So, very temporary values (between function calls) can be used with negative offsets to %rsp. Not all compilers utilize this.
About this code (note opposite order of register movement - there are two main styles of displaying x86 assembly code): pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
the comment was: Compiler allocates some space for local values on function enter. That's why it subtracts value from %rsp on enter. This doesn't depend on whether %rbp is used as frame pointer. After that, this place is used with positive offsets upon %rsp. Also, if this function calls another one, %rsp shall be aligned on 16-byte boundary for each call, so, in that case compiler shall subtract 8 from %rsp on each enter.1. You can figure out things about the assembly even without understanding assembly (e.g. lines of source translating into 0 lines of output vs many lines of output).
2. You have labels.
3. You can figure out some of the assembly on your own. Say: `mov %r1 %r2` - it probably moves what's in entity %r1 into a similar entity %r2, or vice-versa. etc.
4. You can see what the executable outputs
5. and most importantly: You can read compiler warnings and errors...
Even on JVM and .NET there are ways to dump it, while on the various JVM implementations it requires a plugin if not using a debug build, on the .NET side, you can use show Assembly on Visual Studio, or make use of WinDBG with SOS plugin.
I think most of the confabulation of C++ is necessary to get the semantics needed for it to work right. Especially with all of the optimizations that compilers are expected to make. I found the reasoning behind switching from just rvalues/lvalues to the 5+ types they have now to be fascinating, for example.