154 lines
7.9 KiB
Markdown
154 lines
7.9 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[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
|