Live data from Hacker News

Execution units are often pipelined

blog.xoria.org

71–80 of 102 posts

Re: Execution units are often pipelined

#71
post #38

Earlier quoted context omitted.

> I don't think anyone is talking about "fetch, decode, operate, retire" pipelining (though that is certainly called pipelinig): only pipelining within the execution of a instruction that takes multiple cycles just to execute (i.e., latency from input-ready to output-ready). I'm curious what you think the distinction is? Those statements are equivalent. The circuit implementing "an instruction" can't work in a single…

We are interested in the software visible performance effects of pipelining. For small benchmarks that don't miss in the predictors or icache, this mostly means execution pipelining. That's the type of pipelining the article is discussing and the type of pipelining considered in instruction performance breakdowns considered by Agner, uops.info, simulated by LLVM-MCA, etc. I.e., a lot of what you need to model for tig…

Adding to this: the distinction is that an entire "instruction pipeline" can be [and often is] decomposed into many different pipelined circuits. This article is specifically describing the fact that some execution units are pipelined.

Those are different notions of pipelining with different motivations: one is motivated by "instruction-level parallelism," and the other is motivated by "achieving higher clock rates." If 64-bit multiplication were not pipelined, the minimum achievable clock period would be constrained by "how long it takes for bits to propagate through your multiplier."

Re: Execution units are often pipelined

#72
post #59

My favorite illustrations for the concepts discussed here (in an accessible form, not the processor optimization manuals) has long been [0]. For me, this really makes working with a modern microprocessor a science , as anyone who has written benchmarks knows -- it's difficult to reason about the complex behaviour and performance cliffs without testing. Another excellent example of the weirdness has to be JVM anatomy…

The first link is very nice, worth of a submission of its own.

https://hn.algolia.com/?query=Modern%20Microprocessors%20%E2...

Re: Execution units are often pipelined

#75
I think my favorite introduction to just how insane the pipelines, predictors, and other insanity in "modern" (more than a decade ago now) was trying to improve `Math.sqrt()` performance in JSC. This was during the first generation of JIT JS engines (e.g. no one was inlining functions yet), and I was replacing the host implementation of Math.sqrt with a pure assembly version - essentially calling a host function was significantly more expensive another JS function - e.g. JIT JS function -> JIT JS function was significantly faster than JIT JS function -> host code (e.g. C/C++). As part of doing that I was going step by step through each instruction making sure it was the minimum overhead as each step, think something like (very approximate - again more than a decade ago):

  v0:
    1. if (input not a number)
       fallback to C++; else
    2. return tagged 0; // Just making sure the numeric check was optimal

  v1:
    1. As above
    2. If integer
       convert to float
    3. return tagged 0

  v2:
    1-2. as above
    3. If negative
       return tagged nan
    4. Return tagged 0

  v3:
    1-3. as above
    4. use the sqrt instruction
    5. return tagged 0

  v4.
    1-4. as above
    5. move  back to an integer register
    6. return tagged 0

  v5.
    1-5. as above
    6. tag the result of sqrt
    7. return tagged 0

  v6.
    1-6. as above
    7. Actually return/store the result of 
Alas I cannot recall whether at this point return values were going into the heap allocated VM call stack, or whether the return was via rax, but that's not the bit that was eye opening to me.

I had a benchmark that was something like

    for (var i = 0; i 
Noting that while I was working on this there was no meaningful control flow analysis, inlining, etc so this was an "effective" benchmark for perf work at the time - it would not be so today.

The performance remained "really good" (read fake) until the `v6` version that actually store/returned the result of the work. It was incredibly eye opening to see just how much code could be "executed" before the CPU actually ended up doing any work, and significantly impacted my approach to dealing with codegen in future.

My perspective at the time was "I know there's a significant marshaling cost to calling host code, and I know the hardware sqrt is _very_ fast", so it seemed that it was possible that a 5-10x perf improvement seemed "plausible" to me at the time (because marshaling was legitimately very expensive) - and I can't recall where in the 5-10x range the perf improvement was - but then once the final store/return was done it dropped in perf to only 2x faster. Which was still a big win, but also seeing just how much work the CPU could just avoid doing while trying to build out the code was a significant learning experience.

Re: Execution units are often pipelined

#76
post #63
post #42

Earlier quoted context omitted.

Probably to do multiplies, as the extra add is basically free. Adds are cheaper.

Adds are cheaper only for fixed-point computations. Floating point addition needs to denormalize one of its' arguments, perform an (integer) addition and then normalize the result. Usually FP adds take a cycle or two longer than FP multiplication.

Depends on what you mean by ‘cheaper’. Multiplies are still more gates. The adds are slower due to longer dependency chains, not because they cost more gates.

Re: Execution units are often pipelined

#77
post #64

Earlier quoted context omitted.

VLIW is again a different thing. It uses a single instruction that encodes multiple independent operations to simplify decoding and tracking, usually with exposed pipelines. But you can have, for example, a classic in-order RISC design that allows for parallel execution. OoO renaming is not necessary for dependency tracking (in fact even scalar in order CPUs need dependency tracking to solve RAW and other hazards), i…

> VLIW is again a different thing. No it isn't. I'm being very deliberate here with refusing pedantry. In practice , "multiple dispatch" means "OO" in the same way that "VLIW" means "parallel in order dispatch". Yes, you can imagine hypothetical CPUs that mix the distinction, but they'd be so weird that they'd never be built. Discussing the jargon without context only confuses things. > you can have, for example, a c…

> Note that the Pentium had two defined pipes with specific rules about how the pairing was encoded in the instruction stream. It was, in practice, a VLIW architecture (just one with a variable length encoding and where most of the available instruction bundles only filled one slot)!

This is ridiculous. There are no nop-filled slots in the instruction stream, and you can't even be sure which instructions will issue together unless you trace backwards far enough to find a sequence of instructions that can only be executed on port 0 and thus provide a known synchronization point. The P5 only has one small thing in common with VLIW, and there's already a well-accepted name for that feature, and it isn't VLIW.

Re: Execution units are often pipelined

#78
post #48

Earlier quoted context omitted.

Specifically, Carmack exploited the fact that on the Pentium, integer instructions could run in parallel with floating-point division[0]. This goes to show that an optimization that usually gets you ~10% might get you 200% depending on what software you're running. And that no implementation detail is safe from an ambitious software engineer. [0] https://news.ycombinator.com/item?id=38249029

So that was not actually pipelining, but superscalar execution. Incidentally division wasn't pipelined at all.

Pipelining did also play a role. The Quake inner rasterization loop has a decent amount of non-division math in it as well that leverages the Pentium's ability to execute FP add/multiplies at 1/cycle. The K6 and 6x86 FPUs were considerably slower -- 2 and 4 cyclesnon-pipelined (http://www.azillionmonkeys.com/qed/cpuwar.html).

Additionally, the FXCH instructions required to optimally schedule FPU instructions on the Pentium hurt 486/K6/6x86 performance even more since they cost additional cycles. Hard for the 6x86 to keep up when it takes 7 cycles to execute an FADD+FXCH pair vs. 1 for the Pentium.

Re: Execution units are often pipelined

#79
post #59

My favorite illustrations for the concepts discussed here (in an accessible form, not the processor optimization manuals) has long been [0]. For me, this really makes working with a modern microprocessor a science , as anyone who has written benchmarks knows -- it's difficult to reason about the complex behaviour and performance cliffs without testing. Another excellent example of the weirdness has to be JVM anatomy…

The first link is very nice, worth of a submission of its own.

It was very good, I learned a lot. Actually more high-quality info than I can absorb in one sitting, but I bookmarked it to come back to.

I'd love to see an updated version. The article talks about how out-of-order execution has a high power overhead and produces relatively small performance gains, but the M-series chips from Apple have deep OOO and low power consumption; I'm curious to learn what they did differently.

Re: Execution units are often pipelined

#80

If the two ALUs could feed results into each other, that would make things really interesting, especially when you consider the way it would affect reordering. Imagine if the OP's benchmark had two of those multiplication chains on independent registers: mul x1, x0, x0 // a mul x2, x1, x1 // b mul x3, x2, x2 // c mul x4, x3, x3 // d mul x6, x5, x5 // e mul x7, x6, x6 // f mul x8, x7, x7 // g mul x9, x8, x8 // h If yo…

Closest I can think of would be the original Pentium 4, which had double-speed integer ALUs where simple operations like addition would produce the low 16 bits in the first half cycle and the high 16 bits in the second half cycle. A dependent op could immediately start using the low result before the high result was done to give effective 0.5c latency and throughput and 4 adds/cycle. Intel dropped this in the 64-bit capable revision of the P4, though.
Post reply on HN