abrash-black-book/11-02.md
2013-12-30 20:26:41 +11:00

141 lines
No EOL
7.7 KiB
Markdown

You might think that the elimination of the 8-bit bus cycle-eater would
mean that the prefetch queue cycle-eater would also vanish, since on the
8088 the prefetch queue cycle-eater is a side effect of the 8-bit bus.
That would seem all the more likely given that both the 286 and the 386
have larger prefetch queues than the 8088 (6 bytes for the 286, 16 bytes
for the 386) and can perform memory accesses, including instruction
fetches, in far fewer cycles than the 8088.
However, the prefetch queue cycle-eater *doesn't* vanish on either the
286 or the 386, for several reasons. For one thing, branching
instructions still empty the prefetch queue, so instruction fetching
still slows things down after most branches; when the prefetch queue is
empty, it doesn't much matter how big it is. (Even apart from emptying
the prefetch queue, branches aren't particularly fast on the 286 or the
386, at a minimum of seven-plus cycles apiece. Avoid branching whenever
possible.)
After a branch it *does* matter how fast the queue can refill, and there
we come to the second reason the prefetch queue cycle-eater lives on:
The 286 and 386 are so fast that sometimes the Execution Unit can
execute instructions faster than they can be fetched, even though
instruction fetching is *much* faster on the 286 and 386 than on the
8088.
(All other things being equal, too-slow instruction fetching is more of
a problem on the 286 than on the 386, since the 386 fetches 4
instruction bytes at a time versus the 2 instruction bytes fetched per
memory access by the 286. However, the 386 also typically runs at least
twice as fast as the 286, meaning that the 386 can easily execute
instructions faster than they can be fetched unless very high-speed
memory is used.)
The most significant reason that the prefetch queue cycle-eater not only
survives but prospers on the 286 and 386, however, lies in the various
memory architectures used in computers built around the 286 and 386. Due
to the memory architectures, the 8-bit bus cycle-eater is replaced by a
new form of the wait state cycle-eater: wait states on accesses to
normal system memory.
#### System Wait States {#Heading6 align="center"}
The 286 and 386 were designed to lose relatively little performance to
the prefetch queue cycle-eater...*when used with zero-wait-state
memory:* memory that can complete memory accesses so rapidly that no
wait states are needed. However, true zero-wait-state memory is almost
never used with those processors. Why? Because memory that can keep up
with a 286 is fairly expensive, and memory that can keep up with a 386
is *very* expensive. Instead, computer designers use alternative memory
architectures that offer more performance for the dollar—but less
performance overall—than zero-wait-state memory. (It *is* possible to
build zero-wait-state systems for the 286 and 386; it's just so
expensive that it's rarely done.)
The IBM AT and true compatibles use one-wait-state memory (some AT
clones use zero-wait-state memory, but such clones are less common than
one-wait-state AT clones). The 386 systems use a wide variety of memory
systems—including high-speed caches, interleaved memory, and
static-column RAM—that insert anywhere from 0 to about 5 wait states
(and many more if 8 or 16-bit memory expansion cards are used); the
exact number of wait states inserted at any given time depends on the
interaction between the code being executed and the memory system it's
running on.
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *The performance of most 386 memory systems can vary greatly from one memory access to another, depending on factors such as what data happens to be in the cache and which interleaved bank and/or RAM column was accessed last.*
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
The many memory systems in use make it impossible for us to optimize for
286/386 computers with the precision that's possible on the 8088.
Instead, we must write code that runs reasonably well under the varying
conditions found in the 286/386 arena.
The wait states that occur on most accesses to system memory in 286 and
386 computers mean that nearly every access to system memory—memory in
the DOS's normal 640K memory area—is slowed down. (Accesses in computers
with high-speed caches may be wait-state-free if the desired data is
already in the cache, but will certainly encounter wait states if the
data isn't cached; this phenomenon produces highly variable instruction
execution times.) While this is our first encounter with system memory
wait states, we have run into a wait-state cycle-eater before: the
display adapter cycle-eater, which we discussed along with the other
8088 cycle-eaters way back in Chapter 4. System memory generally has
fewer wait states per access than display memory. However, system memory
is also accessed far more often than display memory, so system memory
wait states hurt plenty—and the place they hurt most is instruction
fetching.
Consider this: The 286 can store an immediate value to memory, as in
**MOV [WordVar],0**, in just 3 cycles. However, that instruction is 6
bytes long. The 286 is capable of fetching 1 word every 2 cycles;
however, the one-wait-state architecture of the AT stretches that to 3
cycles. Consequently, nine cycles are needed to fetch the six
instruction bytes. On top of that, 3 cycles are needed to write to
memory, bringing the total memory access time to 12 cycles. On balance,
memory access time—especially instruction prefetching—greatly exceeds
execution time, to the extent that this particular instruction can take
up to four times as long to run as it does to execute in the Execution
Unit.
And that, my friend, is unmistakably the prefetch queue cycle-eater. I
might add that the prefetch queue cycle-eater is in rare good form in
the above example: A 4-to-1 ratio of instruction fetch time to execution
time is in a class with the best (or worst!) that's found on the 8088.
Let's check out the prefetch queue cycle-eater in action. Listing 11.1
times **MOV [WordVar],0**. The Zen timer reports that on a
one-wait-state 10 MHz 286-based AT clone (the computer used for all
tests in this chapter), Listing 11.1 runs in 1.27 µs per instruction.
That's 12.7 cycles per instruction, just as we calculated. (That extra
seven-tenths of a cycle comes from DRAM refresh, which we'll get to
shortly.)
**LISTING 11.1 L11-1.ASM**
;
; *** Listing 11.1 ***
;
; Measures the performance of an immediate move to
; memory, in order to demonstrate that the prefetch
; queue cycle-eater is alive and well on the AT.
;
jmp Skip
;
even ;always make sure word-sized memory
; variables are word-aligned!
WordVar dw 0
;
Skip:
call ZTimerOn
rept 1000
mov [WordVar],0
endm
call ZTimerOff
What does this mean? It means that, practically speaking, the 286 as
used in the AT doesn't have a 16-bit bus. From a performance
perspective, the 286 in an AT has two-thirds of a 16-bit bus (a 10.7-bit
bus?), since every bus access on an AT takes 50 percent longer than it
should. A 286 running at 10 MHz *should* be able to access memory at a
maximum rate of 1 word every 200 ns; in a 10 MHz AT, however, that rate
is reduced to 1 word every 300 ns by the one-wait-state memory.