Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
170
21-02.md
Normal file
170
21-02.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](21-01.html) [Table of Contents](index.html) [Next](21-03.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
As on the 486, you should keep a careful eye out for AGIs involving the
|
||||
stack pointer. Implicit modifiers of ESP, such as **PUSH** and **POP**,
|
||||
are special-cased so you don’t have to worry about AGIs. However, if you
|
||||
explicitly modify ESP with this instruction
|
||||
|
||||
sub esp,100h
|
||||
|
||||
for example, or with the popular
|
||||
|
||||
mov esp,ebp
|
||||
|
||||
you can then get AGIs if you attempt to use ESP to address memory,
|
||||
either explicitly with instructions like this one
|
||||
|
||||
moveax,[esp+20h]
|
||||
|
||||
or via **PUSH**, **POP**, or other instructions that implicitly use ESP
|
||||
as an addressing register.
|
||||
|
||||
On the 486, any instruction that had both a constant value and an
|
||||
addressing displacement, such as
|
||||
|
||||
mov dword ptr [ebp+16],1
|
||||
|
||||
suffered a 1-cycle penalty, taking a total of 2 cycles. Such
|
||||
instructions take only one cycle on the Pentium, but they cannot pair,
|
||||
so they’re still the most expensive sort of **MOV**. Knowing this can
|
||||
speed up something as simple as zeroing two memory variables, as in
|
||||
|
||||
sub eax,eax ;U-pipe 1
|
||||
;any V-pipe pairable
|
||||
; instruction can go here,
|
||||
; or SUB could be in V-pipe
|
||||
mov [MemVar1],eax ;U-pipe 2
|
||||
mov [MemVar2],eax ;V-pipe 2
|
||||
|
||||
which should never be slower and should potentially be 0.5 cycles
|
||||
faster, and six bytes smaller than this sequence:
|
||||
|
||||
mov [MemVar1],0 ;U-pipe 1
|
||||
mov [MemVar2],0 ;U-pipe 2
|
||||
|
||||
Note, however, that my experiments thus far indicate that the two writes
|
||||
in the first case don’t actually pair (possibly because the memory
|
||||
variables have never been read into the internal cache), so you might
|
||||
want to insert an instruction between the two **MOV**s—and, of course,
|
||||
this is yet another reason why you should always measure your code’s
|
||||
actual performance.
|
||||
|
||||
### Register Contention {#Heading4}
|
||||
|
||||
Finally, we come to the last major component of superscalar
|
||||
optimization: register contention. The basic premise here is simple: You
|
||||
can’t use the same register in two inherently sequential ways in a
|
||||
single cycle. For example, you can’t execute
|
||||
|
||||
inc eax ;U-pipe cycle 1
|
||||
;V-pipe idle cycle 1
|
||||
; due to dependency
|
||||
and ebx,eax ;U-pipe cycle 2
|
||||
|
||||
in a single cycle; **AND EBX,EAX** can’t execute until the value in EAX
|
||||
is known, and that can’t happen until **INC EAX** is done. Consequently,
|
||||
the V-pipe idles while **INC EAX** executes in the U-pipe. We saw this
|
||||
in the last chapter when we discussed splitting instructions into simple
|
||||
instructions, and it is by far the most common sort of register
|
||||
contention, known as read-after-write register contention.
|
||||
Read-after-write register contention is the primary reason we have to
|
||||
interleave independent operations in order to get maximum V-pipe usage.
|
||||
|
||||
The other sort of register contention is known as write-after-write.
|
||||
Write-after-write register contention happens when two instructions try
|
||||
to write to the same register on the same cycle. While that may not seem
|
||||
like a particularly useful operation in general, it can happen when
|
||||
subregisters are being set, as in the following
|
||||
|
||||
sub eax,eax ;U-pipe cycle 1
|
||||
;V-pipe idle cycle 1
|
||||
; due to register contention
|
||||
mov al,[Var] ;U-pipe cycle 2
|
||||
|
||||
where an attempt is made to set both EAX and its AL subregister on the
|
||||
same cycle. Write-after-write contention implies that the two
|
||||
instructions comprising the above substitute for **MOVZX** should have
|
||||
at least one unrelated instruction between them when **SUB EAX,EAX**
|
||||
executes in the V-pipe.
|
||||
|
||||
#### Exceptions to Register Contention {#Heading5}
|
||||
|
||||
Intel has special-cased some very useful exceptions to register
|
||||
contention. Happily, write-after-read operations do *not* cause
|
||||
contention. Such operations, as in
|
||||
|
||||
mov eax,edx ;U-pipe cycle 1
|
||||
sub edx,edxX ;V-pipe cycle 1
|
||||
|
||||
are free of charge.
|
||||
|
||||
Also, stack-related instructions that modify ESP only implicitly
|
||||
(without ESP as part of any explicit operand) do not cause AGIs, and
|
||||
neither do they cause register contention with other instructions that
|
||||
use ESP only implicitly; such instructions include **PUSH *reg/immed*,
|
||||
POP *reg***, and **CALL**. (However, these instructions do cause
|
||||
register contention on ESP—but not AGIs—with instructions that use ESP
|
||||
explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the
|
||||
following sequence would hardly use the V-pipe at all:
|
||||
|
||||
mov eax,[MemVar] ;U-pipe cycle 1
|
||||
push esi ;V-pipe cycle 1
|
||||
push eax ;U-pipe cycle 2
|
||||
push edi ;V-pipe cycle 2
|
||||
push ebx ;U-pipe cycle 3
|
||||
call FooTilde ;V-pipe cycle 3
|
||||
|
||||
But in fact, all the instructions pair, even though ESP is modified five
|
||||
times in the space of six instructions.
|
||||
|
||||
The final register-contention special case is both remarkable and
|
||||
remarkably important. There is exactly one sort of instruction that can
|
||||
pair only in the V-pipe: branches. Any near call or conditional or
|
||||
unconditional near jump can execute in the V-pipe paired with any
|
||||
pairable U-pipe instruction, as illustrated by this sequence:
|
||||
|
||||
LoopTop:
|
||||
mov [esi],eax ;U-pipe cycle 1
|
||||
add esi,4 ;V-pipe cycle 1
|
||||
dec ecx ;U-pipe cycle 2
|
||||
jnz LoopTop ;V-pipe cycle 2
|
||||
|
||||
Branches can’t pair in the U-pipe; a branch that executes in the U-pipe
|
||||
runs alone, with the V-pipe idle. If a call or jump is correctly
|
||||
predicted by the Pentium’s branch prediction circuitry (as discussed in
|
||||
the last chapter), it executes in a single cycle, pairing if it runs in
|
||||
the V-pipe; if mispredicted, conditional jumps take 4 cycles in the
|
||||
U-pipe and 5 cycles in the V-pipe, and mispredicted calls and
|
||||
unconditional jumps take 3 cycles in either pipe. Note that **RET**
|
||||
can’t pair.
|
||||
|
||||
### Who’s in First? {#Heading6}
|
||||
|
||||
One of the trickiest things about superscalar optimization is that a
|
||||
given instruction stream can execute at a different speed depending on
|
||||
the pipe where it starts execution, because which instruction goes
|
||||
through the U-pipe first determines which of the following instructions
|
||||
will be able to pair. If we take the last example and add one more
|
||||
instruction, the other instructions will go through different pipes than
|
||||
previously, and cause the loop as a whole to take 50 percent longer,
|
||||
even though we only added 25 percent more cycles:
|
||||
|
||||
LoopTop:
|
||||
inc edx ;U-pipe cycle 1
|
||||
mov [esi],eax ;V-pipe cycle 1
|
||||
add esi,4 ;U-pipe cycle 2
|
||||
dec ecx ;V-pipe cycle 2
|
||||
jnz LoopTop ;U-pipe cycle 3
|
||||
;V-pipe idle cycle 3
|
||||
; because JNZ can’t
|
||||
; pair in the U-pipe
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](21-01.html) [Table of Contents](index.html) [Next](21-03.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue