X86 Machine Code Statistics
strchr.com
strchr.com
B) Obviously the instruction mix depends on the programs analyzed. The ones he analyzed are actually all heavily tuned but smaller programs. I wonder if that changes in other domains. If he ever re-does his analysis, it would be interesting to see something gigantic like OpenOffice, which I'm guessing might even be more MOV based. And maybe something like Octave, which might make use of some more esoteric math functions.
C) When he says "three popular open-source applications were disassembled and analysed with a Basic script", I thought he might mean "simple", but downloading the source, he does indeed mean BASIC the programming language. :)
This can be useful when building up expected performance models of a system.
X86_64 ABI switched to being able to pass arguments in registers as well. And the top of the stack will almost certainly be in cache anyway for the old style.
They are nowhere near as expensive as a naive analysis would suggest - but they are nowhere near a free lunch either.
Sucks for them that Visual Studio peaked when they were still in diapers.
https://scontent-fra3-1.xx.fbcdn.net/hphotos-xpt1/v/t1.0-9/1...
Instructions that are present very rarely might be executed far more frequently if they are within a loop, particularly a tight number-crunching loop.
I suspect that MOV instructions would still dominate but that you would see then "minorities" within the instruction set getting more representation in the stats.
We just finished profiling our 1M LOC C++ codebase and it shows even more extreme numbers; we hit close to 95% of the time spent in 5% of the code.
Ideally, register-register MOV instructions on a 2-address architecture would be necessary only in the case of requiring two copies of some data, since the register/memory/immediate operands of other instructions can already be used to choose which registers should hold what data.
The fact that such a huge percentage of the instructions are simply moving data around suggests two possible explanations: these could be obligatory MOVs for making copies of data, meaning that a 2-address architecture is too limiting and a 3-address one would be more ideal, or they could be superfluous, meaning that 2-address is a sweet spot and compilers are generating lots more explicit data movement than they should. From my experience writing Asm and looking at typical data flow in applications, I'm tempted to lean toward the latter...
Calling conventions are similar (e.g. eax needs to be the return value), so you see things like this:
mov ecx, [ebp+16]
add ecx, edx
mov eax, ecx ; superfluous move
ret
when this could've been done instead: mov eax, [ebp+16]
add eax, edx
retHigher quality compilers will also try things like commuting instructions, using lea as a three-address add, etc to avoid a move.
[1] Some recent progress on register allocation over SSA may improve this situation, but I don't think that work has made it into production compilers yet.
More to the point, it's unclear if this is a static analysis of the binary, or a runtime analysis of retired instructions.
/usr/bin $ for f in *; do objdump -d $f | sed -e 's/^ *[0-9a-f]*:[\t 0-9a-f]*[ \t]\([a-z][0-9a-z][0-9a-z][0-9a-z]*\)[ \t]\(.*\)$/\1/g' | grep '^[a-z0-9]*$' >> /tmp/instrs.txt; done
/usr/bin $ cat /tmp/instrs.txt | awk '/./ { arrs[$1] += 1 } END { for (val in arrs) { print arrs[val], val; sum += arrs[val] } print sum, "Total" }' | sort -n -r | head -n 50
For those of you who can't read bash, this basically amounts to:1. Find every ELF binary in /usr/bin
2. Use objdump to disassemble the text sections
3. Grep for what the commands probably are (not 100% accurate, since some constant data (e.g., switch tables) gets inlined into the text section, and some nop sequences get mangled)
4. Build a histogram of the instructions, and then print the results
I don't bother to try to handle the different sizes of types (particularly for nop, which can be written in several different ways to align the next instruction on the right boundary). The result is this list:
117336056 Total
43795946 mov
9899354 callq
7128602 lea
5258131 test
4928828 cmp
4463038 jmpq
3879175 pop
3628965 jne
3519259 add
3227188 xor
2824699 push
2361085 nopl
2016346 sub
1678131 and
1551014 movq
1311683 jmp
1311476 retq
1255216 nopw
1239479 movzbl
1234815 movl
946434 movb
663852 shr
614367 shl
581608 cmpb
523398 movslq
427116 pushq
384794 cmpq
376695 jbe
348158 movsd
341248 testb
340718 sar
338542 xchg
311171 data16
302296 jle
266539 movzwl
252872 cmpl
210762 jae
169823 lock
152724 addq
151186 sete
147340 cmove
146501 imul
146386 setne
145233 movabs
142801 repz
123489 cmovne
123439 addl
105156 pxor
81745 cmpw
Since this is Debian Linux, nearly every binary is compiled with gcc. The push and pop instructions are therefore relatively rare (since they tend not to be used to set up call instructions, just mov's to the right position on rbp/rsp). jmpq and pushq are way overrepresented thanks to the PLT relocations (2 jmpq, 1 pushq per PLT entry).mov's are very common because, well, they mean several different things in x86-64: set a register to a given constant value, copy from one register to another, load from memory into a register, store from a register into memory, store a constant into memory... Note, too, that several x86 instructions require particular operands to be in particular registers (e.g., rdx/rax for div, not to mention function calling conventions).
If you're curious, the rarest instructions I found were the AES and CRC32 instructions.
http://www.realworldtech.com/haswell-cpu/4/
And older, about amd64 in general (x86/32bit is quite different from 64bit):
"lea -0x30(%edx,%esi,8),%esi
Compute the address %edx+8*%esi-48, but don’t refer to the contents of memory. Instead, store the address itself into register %esi. This is the “load effective address” instruction: its binary coding is short, it doesn’t tie up the integer unit, and it doesn’t set the flags."
http://people.freebsd.org/~lstewart/references/amd64.pdf
[ed: in general the purpose of lea is to get the address of things in c/pascal arrays (pointer to array+offset) -- as I understand it. I don't know if it's used for other tricks by optimizing (c) compilers much -- but taking the address of an item in an array (say a character in a C string) sounds like it would be fairly common. If lea is fast(er) on amd64 using the dedicated operand would make sense.]
9899354 callq
3879175 pop
2824699 push
1311476 retq
I suppose many of the callq goes into the kernel/libc, so the coresponding ret/retq wouldn't be in the binaries?Interesting that pop ~ 2x push. Perhaps this is due to many off the callq's leading to something interesting being left on the stack?
< file od
for example works just as well.1. A combination of static "number of instruction"s, as here, number of times each instruction is executed, and amount of time spent executing each instruction.
2. Statistics for a Markov chain - i.e. not just what single instructions are common, but what combinations of 2 / 3 / + instructions are common. Preferably both in-order and set-based.
argh noooo. Whole analysis is failed :( It doesnt matter that 50% of binary is movs if said binary executes them only once, or not at all (some obscure function). It would be more valuable to test those, or any other program, in modified QEMU TCG or MESS/PCem.