142 lines
No EOL
7.3 KiB
Markdown
142 lines
No EOL
7.3 KiB
Markdown
#### 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. |