Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
165
63-02.md
Normal file
165
63-02.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](63-01.html) [Table of Contents](index.html) [Next](63-03.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
FDIV is a painfully slow instruction, taking 39 cycles at full precision
|
||||
and 33 cycles at double precision, which is the default precision for
|
||||
Visual C++ 2.0. While FDIV executes, the FPU is occupied, and can’t
|
||||
process subsequent FP instructions until FDIV finishes. However, during
|
||||
the cycles while FDIV is executing (with the exception of the one cycle
|
||||
during which FDIV starts), the integer unit can simultaneously execute
|
||||
instructions other than IMUL. (IMUL uses the FPU, and can only overlap
|
||||
with FDIV for a few cycles.) Since the integer unit can execute two
|
||||
instructions per cycle, this means it’s possible to have three
|
||||
instructions, an FDIV and two integer instructions, executing at the
|
||||
same time. That’s exactly what happens, for example, during the second
|
||||
cycle of this code:
|
||||
|
||||
FDIV ST(0),ST(1)
|
||||
ADD EAX,ECX
|
||||
INC EDX
|
||||
|
||||
There’s an important limitation, though; if the instruction stream
|
||||
following the FDIV reaches a FP instruction (or an IMUL), then that
|
||||
instruction and all subsequent instructions, both integer and FP, must
|
||||
wait to execute until FDIV has finished.
|
||||
|
||||
When a FADD, FSUB, or FMUL instruction is executed, it is 3 cycles
|
||||
before the result can be used by another instruction. (There’s an
|
||||
exception: If the instruction that attempts to use the result is an FST
|
||||
to memory, there’s an extra cycle lost, so it’s 4 cycles from the start
|
||||
of an arithmetic instruction until an FST of that value can begin, so
|
||||
|
||||
FMUL ST(0),ST(1)
|
||||
FST [temp]
|
||||
|
||||
takes 6 cycles in all.) Again, it’s possible to execute integer-unit
|
||||
instructions during the 2 (or 3, for FST) cycles after one of these FP
|
||||
instructions starts. There’s a more exciting possibility here, though:
|
||||
Given properly structured code, the FPU is capable of averaging 1 cycle
|
||||
per FADD, FSUB, or FMUL. The secret is pipelining.
|
||||
|
||||
#### Pipelining, Latency, and Throughput {#Heading5}
|
||||
|
||||
The Pentium’s FPU is the first pipelined x86 FPU. *Pipelining* means
|
||||
that the FPU is capable of starting an instruction every cycle, and can
|
||||
simultaneously handle several instructions in various stages of
|
||||
completion. Only certain x86 FP instructions allow another instruction
|
||||
to start on the next cycle, though: FADD, FSUB, and FMUL are pipelined,
|
||||
but FST and FDIV are not. (FLD executes in a single cycle, so pipelining
|
||||
is not an issue.) Thus, in the code sequence
|
||||
|
||||
FADD1
|
||||
FSUB
|
||||
FADD2
|
||||
FMUL
|
||||
|
||||
FADD~1~ can start on cycle N, FSUB can start on cycle N+1, FADD~2~ can
|
||||
start on cycle N+2, and FMUL can start on cycle N+3. At the start of
|
||||
cycle N+3, the result of FADD~1~ is available in the destination
|
||||
operand, because it’s been 3 cycles since the instruction started; FSUB
|
||||
is starting the final cycle of calculation; FADD~2~ is starting its
|
||||
second cycle, with one cycle yet to go after this; and FMUL is about to
|
||||
be issued. Each of the instructions takes 3 cycles to produce a result
|
||||
from the time it starts, but because they’re simultaneously processed at
|
||||
different pipeline stages, one instruction is issued and one instruction
|
||||
completes every cycle. Thus, the latency of these instructions—that is,
|
||||
the time until the result is available—is 3 cycles, but the
|
||||
throughput—the rate at which the FPU can start new instructions—is 1
|
||||
cycle. An exception is that the FPU is capable of starting an FMUL only
|
||||
every 2 cycles, so between these two instructions
|
||||
|
||||
FMUL ST(1),ST(0)
|
||||
FMUL ST(2),ST(0)
|
||||
|
||||
there’s a 1-cycle stall, and the following three instructions execute
|
||||
just as fast as the above pair:
|
||||
|
||||
FMUL ST(1),ST(0)
|
||||
FLD ST(4)
|
||||
FMUL ST(0),ST(1)
|
||||
|
||||
There’s a caveat here, though: A FP instruction can’t be issued until
|
||||
its operands are available. The FPU can reach a throughput of 1 cycle
|
||||
per instruction on this code
|
||||
|
||||
FADD ST(1),ST(0)
|
||||
FLD [temp]
|
||||
FSUB ST(1),ST(0)
|
||||
|
||||
because neither the FLD nor the FSUB needs the result from the FADD.
|
||||
Consider, however
|
||||
|
||||
FADD ST(0),ST(2)
|
||||
FSUB ST(0),ST(1)
|
||||
|
||||
where the ST(0) operand to FSUB is calculated by FADD. Here, FSUB can’t
|
||||
start until FADD has completed, so there are 2 stall cycles between the
|
||||
two instructions. When dependencies like this occur, the FPU runs at
|
||||
latency rather than throughput speeds, and performance can drop by as
|
||||
much as two-thirds.
|
||||
|
||||
#### FXCH {#Heading6}
|
||||
|
||||
One piece of the puzzle is still missing. Clearly, to get maximum
|
||||
throughput, we need to interleave FP instructions, such that at any one
|
||||
time ideally three instructions are in the pipeline at once. Further,
|
||||
these instructions must not depend on one another for operands. But
|
||||
ST(0) must always be one of the operands; worse, FLD can only push into
|
||||
ST(0), and FST can only store from ST(0). How, then, can we keep three
|
||||
independent instructions going?
|
||||
|
||||
The easy answer would be for Intel to change the FP registers from a
|
||||
stack to a set of independent registers. Since they couldn’t do that,
|
||||
thanks to compatibility issues, they did the next best thing: They made
|
||||
the FXCH instruction, which swaps ST(0) and any other FP register,
|
||||
virtually free. In general, if FXCH is both preceded and followed by FP
|
||||
instructions, then it takes *no* cycles to execute. (Application Note
|
||||
500, “Optimizations for Intel’s 32-bit Processors,” February 1994,
|
||||
available from [http://www.intel.com](http://www.intel.com), describes
|
||||
all .the conditions under which FXCH is free.) This allows you to move
|
||||
the target of a pending operation from ST(0) to another register, at the
|
||||
same time bringing another register into ST(0) where it can be used, all
|
||||
at no cost. So, for example, we can start three multiplications, then
|
||||
use FXCH to swap back to start adding the results of the first two
|
||||
multiplications, without incurring any stalls, as shown in Listing 63.1.
|
||||
|
||||
**Listing 63.1 L63-1.ASM**
|
||||
|
||||
; use of fxch to allow addition of first two; products to start while third : multiplication finishes
|
||||
fld [vec0+0] ;starts & ends on cycle 0
|
||||
fmul [vec1+0] ;starts on cycle 1
|
||||
fld [vec0+4] ;starts & ends on cycle 2
|
||||
fmul [vec1+4] ;starts on cycle 3
|
||||
fld [vec0+8] ;starts & ends on cycle 4
|
||||
fmul [vec1+8] ;starts on cycle 5
|
||||
fxch st(1) ;no cost
|
||||
faddp st(2),st(0) ;starts on cycle 6
|
||||
|
||||
### The Dot Product {#Heading7}
|
||||
|
||||
Now we’re ready to look at fast FP for common 3-D operations; we’ll
|
||||
start by looking at how to speed up the dot product. As discussed in
|
||||
Chapter 30, the dot product is heavily used in 3-D to calculate cosines
|
||||
and to project points along vectors. The dot product is calculated as d
|
||||
= u~1~v~1~ + u~2~v~2~ + u~3~v~3~; with three loads, three multiplies,
|
||||
two adds, and a store, the theoretical minimum time for this calculation
|
||||
is 10 cycles.
|
||||
|
||||
Listing 63.2 shows a straightforward dot product implementation. This
|
||||
version loses 7 cycles to stalls. Listing 63.3 cuts the loss to 5 cycles
|
||||
by doing all three FMULs first, then using FXCH to set the third FXCH
|
||||
aside to complete while the results of the first two FMULs, which have
|
||||
completed, are added. Listing 43.3 still loses 50 percent to stalls, but
|
||||
unless some other code is available to be interleaved with the dot
|
||||
product code, that’s all we can do to speed things up. Fortunately, dot
|
||||
products are often used in contexts where there’s plenty of interleaving
|
||||
potential, as we’ll see when we discuss transformation.
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](63-01.html) [Table of Contents](index.html) [Next](63-03.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue