It's called "micro-fusion", by the way, because the µops are fused together from the point of view of parts of the processor, but then "unlaminated" to be executed. The alternative is "macro-fusion", where certain instruction pairs (cmp-jcc) are fused by the decoder and treated as one after that. Using Intel's terms, RMW-type instructions are indeed always handled internally using micro-fusion.
As far as micro-fusion, there are at at least three interesting possibilities for the uops originating from the same instruction (with a memory operand):
1) No micro-fusion at all, e.g., for shrx and friends. The number of fused domain and unfused domain uops is the same.
2) Fusion in the uop cache (and probably the IDQ), but not the RAT and later stages. This is a case where the instructions are fused at decode, but are "unlaminated" before rename, and hence it usually has similar performance to no fusion at all (but it does save space in the uop cache), since RAT is more likely to be a performance limitation. If you look at the performance counters to determine fusion, this will look like case (1) (unless you check the DSB uops delivered one, I guess).
3) Full micro-fusion. This is the "normal" micro-fusion that people usually talk about: fusion persists through the RAT and get split up for execution.
As far as I know, "unlamination" refers to the process that distinguishes between type (2) and type (3) - the former are unlaminated and the latter aren't. The existence of type (2) isn't even really clear from the classic sources like Agner's doc, and the behavior has changed even on recent archs (e.g., Skylake unlaminates less scnearios than Haswell).
All the gory details are here:
https://stackoverflow.com/q/26046634/149138
About RMW ops, it usually ends up as 4 unfused uops (store data, store AGU, load, ALU) which get fused into two micro-fused pairs (the store, and the load-ALU) op, not fully fused into one uop. The same concerns about type (2) vs type (3) likely apply also, although I haven't tested it.
About RMW I wasn't trying to make any point about terminology - just mentioning pointing out that they are special in that they may micro-fuse twice (although it turns out that I was wrong and inc can't do it - but add can), unlike load-op which micro-fuse at most once.
So as it turns out, there is zero extra micro-fusion going on in the "fused" vs "unfused" examples, because of the limitation wrt inc. There is still micro-fusion of the store part, but both examples get that equally.
The implication is that compilers are probably better off using "add [mem], 1" in some cases over inc, since it fuses better. It wouldn't actually fuse better in this case though due to the unlamination - but if you could simplify the addressing mode it would. I wrote a reply on the blog laying this out in more detail but unfortunately Disqus "detected it as spam".
> Micro-fusion fuses multiple micro-ops from the same instruction into a single complex micro-op.
And that:
> Macro-fusion merges two instructions to a single micro-op.
The point is that the three instruction format and the single instruction format you would think would decode both to three micro-ops (or something like that), but instead the single instruction format can use a single fused micro op. That's why it's better to write as a single instruction.
And your reference backs this up - it talks about single instructions in this section on micro-op fusion.
You may be confusing it with macro-fusion, which is about fusing multiple logical instructions, which is discussed in the next section in that Agner document, but isn't discussed in this blog.
> This article is instead about whether a particular operation is better written as a single-instruction
Yes - it's better to write as a single-instruction, because then micro-op fusion applies. Macro-op fusion isn't as strong, as doesn't apply to the three logical instructions.
This is not exactly what happens. A read-modify-write instruction in general results in 2 fused uops, one for the read+op, the other for the address generation+write.
In the specific case of INC, you still have 3 fused uops for unclear reasons---possibly because INC only partially changes the flags. Replacing the expression by ADD [memory], 1 results on the expected 2 fused ops.
Edit:
According the this pdf, macro/micro fusion happens very early on macro decoding, but I'm still not sure how it counts retired uops:
http://pc.watch.impress.co.jp/video/pcw/docs/601/161/p21.pdf
And does this mean micro fusion cannot occur when you use the separate load/op/store instructions?
And yes, if you do mov r, m / add r, 1 / mov m, r instead of add m, 1 no such fusion happens. This is specific to CISCy instructions that do more than one thing at the same time.
[1] https://software.intel.com/sites/products/documentation/docl...
[2] https://software.intel.com/sites/products/documentation/docl...
UOPS_RETIRED.RETIRE_SLOTS is fused domain, while UOPS_RETIRED.ALL/UOPS_RETIRED.ANY and friends are unfused domain.
Micro-fusion can happen only for stores, or when instructions have at least one memory source argument. On x86 this means either load-op instructions, which have a memory _source_ argument like:
add eax, [rdx]
... or the so-called RMW instructions, which use memory as source and destination (sometimes with another non-memory source as in the first example) like: add [rdx], 1 ; memory source/dest, immediate 2nd source
dec [rdx] ; memory source/dest only
In these cases fusion happens because the memory access uop(s) can be grouped together with the ALU op, because the memory source is "anonymous" (doesn't get put into a visible register, so no-one can later use it - meaning it doesn't need a named register, hence renaming resources).The decomposition of the first add example into separate instructions looks like:
mov ecx, [rdx]
add eax, ecx
This doesn't fit the pattern above so can't be micro-fused. in particular, the use of ecx as a temporary register here means you couldn't really fuse it: the value in ecx is live after this code segment, so it needs a named physical register.The third case of fusion is stores:
mov [rdx], eax
Internally this uses two uops (one address-generation and one store-data), but they are generally fused so it executes as one uop in the fused domain. This isn't super interesting since there aren't really alternatives for stores, they always have the above form, so micro-fusion is kind of less interesting (unlike the load-op ALU stuff, where you could use memory source, or not).Finally we get to RMW. This is logically a load-op combined with a store. So you have 4 unfused uops in total! Here we potentially get both types of fusion: the load-op fusion, and the store fusion, reducing the number of fused uops to 2.
So to actually answer your question: if you implement a RMW has a separate load, ALU op, store like:
; instead of dec DWORD [rdx]
mov eax, [rdx]
dec eax
mov [rdx], eax
You would lose _half_ of the available fusion: the load-op part is now two instructions, so you don't get the fusion there, but you still get the store fusion. So you have 3 fused-domain uops and 4 unfused, versus 2 and 4 for the RMW case.On the other hand, for cases where you aren't writing the result back immediately to the same memory location, RMW doesn't apply, and you can get maximum fusion by using the "load-op" for of the instruction.
If you use the three-instruction sequence the load and ALU op can't fuse, which potentially makes it slower (but not in this case since the bottleneck is elsewhere).
It's quite unusual that add gets the 2-uop treatment but inc doesn't. Yes, they treat flags differently, but that's mostly been resolved though flag renaming, and the reg forms of inc don't suffer any penalty.
I'll have to double check if this is true. If it is, compilers should generally be preferring add [mem], 1 then (except perhaps when optimizing for size) - the difference in the flag behavior is pretty much never relevant for compiled code.
inc [rdi+rbx]
has the obvious rdi and rbx dependencies, the flags register, plus (presumably, depending on implementation details) an allocated virtual register for the 1 that is added. On the register forms this limit is never a bottleneck.You also see the same behavior, according to Agner, on SHR/SHL m, i, which may or may not alter some flags depending on shift amounts, and strangely on NOT m, which explicitly does not alter the flags in any situation. This latter one makes little sense.
I doubt there is a virtual register for the 1 really - sure there is some storage for it somewhere in the ROB or the scheduler or whatever, but it doesn't need to be "renamed" in the usual sense since it's not visible anywhere. In any case, the add case is "worse" since it can have a real architectural register there, not just an implied immediate 1.
Yes, there is a definitely a limit on the number of inputs a uop can have - and you can see this in the effect of "unlamination" which is where a uop fuses in the uop cache, but then unfuses after that and so mostly acts like an unfused uop (except for uop cache space). This shows up with indexed addressing modes.
For example:
add [rax], 1
fully double-fuses, but: add [rax + rbx], 1
Double-fuses only in the uop cache (counts as 2 there), but unlaminates once after that (counts as 3 in the rest of the fused domain).Interestingly though this guy:
add [rax], rbx
Still fully double-fuses everywhere, despite having the same number of input registers as the add [rax + rbx], case. Probably it's easier for the renamer though because the registers are spread across the uops more evenly rather than being concentrated in the load uop?Moving away from RMW to load-op there are other indications flags aren't a problem: things like BMI shrx/shlx/rorx with memory operand don't fuse despite that these don't update flags at all. On the other and ANDN, which is similarly in BMI and is also 3-arg instruction (distinct src/dest) and updates flags does fuse! So actually I'd say updating the flags in a consistent way makes it more likely to fuse.
Maybe that's the answer then?
Anything that updates the flags in the "standard way" - i.e., SF,ZF,CF,OF all set to something, can (potentially) micro-fuse. Anything which doesn't - whether that is updating fewer flags (inc) or no flags (shrx) or updating them "weirdly" (shl and friends) isn't eligible. Interesting theory and still consistent in broad strokes with your "it's the flags!" claim.
So it is about micro-fusion in that way.
The confusing part is that he mentions a performance different up front, so you'd expect this to be about how the micro-fused version was faster: but that difference was actually because of alignment probably causing a lack of macro-fusion in some cases (or some other alignment effect). The two versions have different alignment, but it's basically just a fluke which one happens to be faster, it could have been the other way around with some unrelated changes to other code that shifted the alignment.
Probably it was due to autocorrect but I chuckled at the misspelling of Agner nonetheless.