Agner Fog's optimization guides are excellent. He has guides, tables of instruction timings, and docs on the microarchitecture of all recent x86 CPU designs (going back as far as Intel Pentium). See also some other resources linked from http://stackoverflow.com/tags/x86/info
Just for fun, I'll answer some of the questions (numbers from recent Intel CPUs). Choice of ops is not the major factor in optimizing code (unless you can avoid division.)
Is a single multiply slower on the CPU than an addition?
Yes (unless it's by a power of 2). (3-4x the latency, and uses more execution resources.) Don't go far out of your way to avoid it, though, since it's as fast as 2 or 3 adds.
Exactly what are the speed characteristics of the basic math and control flow opcodes?
See Agner Fog's instruction tables. Be careful with conditional jumps. Unconditional jumps (like function calls) have some small overhead, but not much.
If two opcodes take the same number of cycles to execute, then both can be used interchangeably without any performance gain / loss?
Nope, they might compete for the same execution port as something else, or they might not. It depends on what other dependency chains the CPU can be working on in parallel. (In practice, there's not usually any useful decision to be made. It occasionally comes up that you could use a vector shift or a vector shuffle, which run on different ports on Intel CPUs. But shift-by-bytes of the whole register (
PSLLDQ etc.) runs in the shuffle unit.)
Any other technical details you can share regarding x86 CPU performance is appreciated
Agner Fog's microarch docs describe the pipelines of Intel and AMD CPUs in enough detail to work out exactly how many cycles a loop should take per iteration, and whether the bottleneck is uop throughput, a dependency chain, or contention for one execution port. See some of my answers on StackOverflow, like this one or this one.
Also, http://www.realworldtech.com/haswell-cpu/ (and similar for earlier designs) is fun reading if you like CPU design.
Here's your list, sorted correctly for a Haswell CPU, based on my best guestimates. This isn't really a useful way of thinking about things for anything but tuning an asm loop, though. Cache / branch-prediction effects usually dominate, so write your code to have good patterns. Numbers are very hand-waivey, and try to account for high latency, even if throughput is not an issue, or for generating more uops which clog up the pipe for other things to happen in parallel. Esp. the cache / branch numbers are very made-up. Latency matters for loop-carried dependencies, throughput matters when each iteration is independent.
- 1 Bitwise / Integer Addition / Subtraction /
shift and rotate (compile-time const count) /
vector versions of all of these
vector shuffles. (1 to 4 per cycle throughput, 1 cycle latency)
- 1 vector min, max, compare-equal (to create a mask)
- 1.5 load / store (L1 cache hit)
- 2 inserting general-purpose registers into vector elements (
- 2.5 shift / rotate by variable count (2c latency, one per 2c throughput)
- 2.5 Comparison without branching (
y = x ? a : b, or
y = x >= 0) (
test / setcc or
- 3 Integer Multiplication, fp add/sub (and vector versions of the same) (1 or 2 per cycle throughtput, 3 to 5 cycle latency)
- 3 int-> float conversion
- 4 vector int compare-greater-than, vector int/fp mul
- 4 predictable Control flow (predicted branch, call, return)
- 5 vector horizontal ops (e.g.
PHADD adding values within a vector)
- 12? load/store (L3 cache hit)
- 13? Control Flow (poorly-predicted branch)
- 15 (vector)FP Division (10-13c latency, one per 7c throughput)
- 17 int division (yes really, it's slower than FP division, and can't vectorize)
- 17 (vector)FP sqrt
- 20-25? Control Flow (predicted branch)
- 50? FP trig / exp / log
- 100? load/store (cache miss)
- 3000??? read page from file (OS disk cache hit) (making up numbers here)
- 20000??? disk read page (OS disk-cache miss, fast SSD) (totally made-up number)
The relative cost of things on AMD CPUs will be similar, except they have faster integer shifters when the shift-count is variable. AMD CPUs are of course slower on most code, for a variety of reasons.