157 lines
No EOL
7.4 KiB
Markdown
157 lines
No EOL
7.4 KiB
Markdown
---
|
|
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
|
author: Michael Abrash
|
|
date: '1997-07-01'
|
|
isbn: '1576101746'
|
|
publisher: The Coriolis Group
|
|
category: 'Web and Software Development: Game Development,Web and Software Development:
|
|
Graphics and Multimedia Development'
|
|
chapter: '11'
|
|
pages: 210-212
|
|
---
|
|
|
|
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.
|
|
|
|
> 
|
|
> 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**
|
|
|
|
```nasm
|
|
;
|
|
; *** 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. |