Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
154
04-07.md
Normal file
154
04-07.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](04-06.html) [Table of Contents](index.html) [Next](04-08.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
#### The Impact of DRAM Refresh {#Heading17}
|
||||
|
||||
Let’s look at examples from opposite ends of the spectrum in terms of
|
||||
the impact of DRAM refresh on code performance. First, consider the
|
||||
series of **MUL** instructions in Listing 4.9. Since a 16-bit **MUL** on
|
||||
the 8088 executes in between 118 and 133 cycles and is only 2 bytes
|
||||
long, there should be plenty of time for the prefetch queue to fill
|
||||
after each instruction, even after DRAM refresh has taken its slice of
|
||||
memory access time. Consequently, the prefetch queue should be able to
|
||||
keep the Execution Unit well-supplied with instruction bytes at all
|
||||
times. Since Listing 4.9 uses no memory operands, the Execution Unit
|
||||
should never have to wait for data from memory, and DRAM refresh should
|
||||
have no impact on performance. (Remember that the Execution Unit can
|
||||
operate normally during DRAM refreshes so long as it doesn’t need to
|
||||
request a memory access from the Bus Interface Unit.)
|
||||
|
||||
**LISTING 4.9 LST4-9.ASM**
|
||||
|
||||
; Measures the performance of repeated MUL instructions,
|
||||
; which allow the prefetch queue to be full at all times,
|
||||
; to demonstrate a case in which DRAM refresh has no impact
|
||||
; on code performance.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mul ax
|
||||
endm
|
||||
call ZTimerOff
|
||||
|
||||
Running Listing 4.9, we find that each **MUL** executes in 24.72 µs, or
|
||||
exactly 118 cycles. Since that’s the shortest time in which **MUL** can
|
||||
execute, we can see that no performance is lost to DRAM refresh. Listing
|
||||
4.9 clearly illustrates that DRAM refresh only affects code performance
|
||||
when a DRAM refresh forces the Execution Unit of the 8088 to wait for a
|
||||
memory access.
|
||||
|
||||
Now let’s look at the series of **SHR** instructions shown in Listing
|
||||
4.10. Since **SHR** executes in 2 cycles but is 2 bytes long, the
|
||||
prefetch queue should be empty while Listing 4.10 executes, with the
|
||||
8088 prefetching instruction bytes non-stop. As a result, the time per
|
||||
instruction of Listing 4.10 should precisely reflect the time required
|
||||
to fetch the instruction bytes.
|
||||
|
||||
**LISTING 4.10 LST4-10.ASM**
|
||||
|
||||
; Measures the performance of repeated SHR instructions,
|
||||
; which empty the prefetch queue, to demonstrate the
|
||||
; worst-case impact of DRAM refresh on code performance.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
|
||||
Since 4 cycles are required to read each instruction byte, we’d expect
|
||||
each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM
|
||||
refresh. In fact, each **SHR** in Listing 4.10 executes in 1.81 µs,
|
||||
indicating that DRAM refresh is taking 7.4 percent of the program’s
|
||||
execution time. That’s nearly 2 percent more than our worst-case
|
||||
estimate of the loss to DRAM refresh overhead! In fact, the result
|
||||
indicates that DRAM refresh is stealing not 4, but 5.33 cycles out of
|
||||
every 72 cycles. How can this be?
|
||||
|
||||
The answer is that a given DRAM refresh can actually hold up CPU memory
|
||||
accesses for as many as 6 cycles, depending on the timing of the DRAM
|
||||
refresh’s DMA request relative to the 8088’s internal instruction
|
||||
execution state. When the code in Listing 4.10 runs, each DRAM refresh
|
||||
holds up the CPU for either 5 or 6 cycles, depending on where the 8088
|
||||
is in executing the current **SHR** instruction when the refresh request
|
||||
occurs. Now we see that things can get even worse than we thought: *DRAM
|
||||
refresh can steal as much as 8.33 percent of available memory access
|
||||
time—6 out of every 72 cycles—from the 8088.*
|
||||
|
||||
Which of the two cases we’ve examined reflects reality? While either
|
||||
case *can* happen, the latter case—significant performance reduction,
|
||||
ranging as high as 8.33 percent—is far more likely to occur. This is
|
||||
especially true for high-performance assembly code, which uses fast
|
||||
instructions that tend to cause non-stop instruction fetching.
|
||||
|
||||
#### What to Do About the DRAM Refresh Cycle-Eater? {#Heading18}
|
||||
|
||||
*Hmmm.* When we discovered the prefetch queue cycle-eater, we learned to
|
||||
use short instructions. When we discovered the 8-bit bus cycle-eater, we
|
||||
learned to use byte-sized memory operands whenever possible, and to keep
|
||||
word-sized variables in registers. What can we do to work around the
|
||||
DRAM refresh cycle-eater?
|
||||
|
||||
Nothing.
|
||||
|
||||
As I’ve said before, DRAM refresh is an act of God. DRAM refresh is a
|
||||
fundamental, unchanging part of the PC’s operation, and there’s nothing
|
||||
you or I can do about it. If refresh were any less frequent, the
|
||||
reliability of the PC would be compromised, so tinkering with either
|
||||
timer 1 or DMA channel 0 to reduce DRAM refresh overhead is out. Nor is
|
||||
there any way to structure code to minimize the impact of DRAM refresh.
|
||||
Sure, some instructions are affected less by DRAM refresh than others,
|
||||
but how many multiplies and divides in a row can you really use? I
|
||||
suppose that code *could* conceivably be structured to leave a free
|
||||
memory access every 72 cycles, so DRAM refresh wouldn’t have any effect.
|
||||
In the old days when code size was measured in bytes, not K bytes, and
|
||||
processors were less powerful—and complex—programmers did in fact use
|
||||
similar tricks to eke every last bit of performance from their code.
|
||||
When programming the PC, however, the prefetch queue cycle-eater would
|
||||
make such careful code synchronization a difficult task indeed, and any
|
||||
modest performance improvement that did result could never justify the
|
||||
increase in programming complexity and the limits on creative
|
||||
programming that such an approach would entail. Besides, all that effort
|
||||
goes to waste on faster 8088s, 286s, and other computers with different
|
||||
execution speeds and refresh characteristics. There’s no way around it:
|
||||
Useful code accesses memory frequently and at irregular intervals, and
|
||||
over the long haul DRAM refresh always exacts its price.
|
||||
|
||||
If you’re still harboring thoughts of reducing the overhead of DRAM
|
||||
refresh, consider this. Instructions that tend not to suffer very much
|
||||
from DRAM refresh are those that have a high ratio of execution time to
|
||||
instruction fetch time, and those aren’t the fastest instructions of the
|
||||
PC. It certainly wouldn’t make sense to use slower instructions just to
|
||||
reduce DRAM refresh overhead, for it’s *total* execution time—DRAM
|
||||
refresh, instruction fetching, and all—that matters.
|
||||
|
||||
The important thing to understand about DRAM refresh is that it
|
||||
generally slows your code down, and that the extent of that performance
|
||||
reduction can vary considerably and unpredictably, depending on how the
|
||||
DRAM refreshes interact with your code’s pattern of memory accesses.
|
||||
When you use the Zen timer and get a fractional cycle count for the
|
||||
execution time of an instruction, that’s often the DRAM refresh
|
||||
cycle-eater at work. (The display adapter cycleis another possible
|
||||
culprit, and, on 386s and later processors, cache misses and pipeline
|
||||
execution hazards produce this sort of effect as well.) Whenever you get
|
||||
two timing results that differ less or more than they seemingly should,
|
||||
that’s usually DRAM refresh too. Thanks to DRAM refresh, variations of
|
||||
up to 8.33 percent in PC code performance are par for the course.
|
||||
|
||||
### Wait States {#Heading19}
|
||||
|
||||
Wait states are cycles during which a bus access by the CPU to a device
|
||||
on the PC’s bus is temporarily halted by that device while the device
|
||||
gets ready to complete the read or write. Wait states are well and truly
|
||||
the lowest level of code performance. Everything we have discussed (and
|
||||
will discuss)—even DMA accesses—can be affected by wait states.
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](04-06.html) [Table of Contents](index.html) [Next](04-08.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue