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

115 lines
7.3 KiB
Markdown

#### How Fast Is Fast? {#Heading4}
Your first question is likely to be the following: Just how fast is
Listing 37.1? Is it optimized to the hilt or just pretty fast? The quick
answer is: It's *fast*. Listing 37.1 draws lines at a rate of nearly 1
million pixels per second on my 486/33, and is capable of still faster
drawing, as I'll discuss shortly. (The heavily optimized AutoCAD
line-drawing code that I mentioned in the last chapter drew 150,000
pixels per second on an EGA in a 386/16, and I thought I had died and
gone to Heaven. Such is progress.) The full answer is a more complicated
one, and ties in to the principle that if it is broken, maybe that's
okay—and to the principle of looking before you leap, also known as
profiling before you optimize.
When I went to speed up run-length slice lines, I initially manually
converted the last chapter's C code into assembly. Then I streamlined
the register usage and used **REP STOS** wherever possible. Listing 37.1
is that code. At that point, line drawing was surely faster, although I
didn't know exactly how much faster. Equally surely, there were
significant optimizations yet to be made, and I was itching to get on to
them, for they were bound to be a lot more interesting than a basic
C-to-assembly port.
Ego intervened at this point, however. I wanted to know how much of a
speed-up I had already gotten, so I timed the performance of the C code
and compared it to the assembly code. To my horror, I found that I had
not gotten even a two-times improvement! I couldn't understand how that
could be—the C code was decidedly unoptimized—until I hit on the idea of
measuring the maximum memory speed of the VGA to which I was drawing.
Bingo. The Paradise VGA in my 486/33 is fast for a single display-memory
write, because it buffers the data, lets the CPU go on its merry way,
and finishes the write when display memory is ready. However, the
maximum rate at which data can be written to the adapter turns out to be
no more than one byte every microsecond. Put another way, you can only
write one byte to this adapter every 33 clock cycles on a 486/33.
Therefore, no matter how fast I made the line-drawing code, it could
never draw more than 1,000,000 pixels per second in 256-color mode in my
system. The C code was already drawing at about half that rate, so the
potential speed-up for the assembly code was limited to a maximum of two
times, which is pretty close to what Listing 37.1 did, in fact, achieve.
When I compared the C and assembly implementations drawing to normal
system (nondisplay) memory, I found that the assembly code was actually
four times as fast as the C code.
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *In fact, Listing 37.1 draws VGA lines at about 92 percent of the maximum possible rate in my system—that is, it draws very nearly as fast as the VGA hardware will allow. All the optimization in the world would get me less than 10 percent faster line drawing—and only if I eliminated all overhead, an unlikely proposition at best. The code isn't fully optimized, but so what?*
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Now it's true that faster line-drawing code would likely be more
beneficial on faster VGAs, especially local-bus VGAs, and in slower
systems. For that reason, I'll list a variety of potential optimizations
to Listing 37.1. On the other hand, it's also true that Listing 37.1 is
capable of drawing lines at a rate of 2.2 million pixels per second on a
486/ 33, given fast enough VGA memory, so it should be able to drive
almost any non-local-bus VGA at nearly full speed. In short, Listing
37.1 is very fast, and, in many systems, further optimization is
basically a waste of time.
Profile before you optimize.
#### Further Optimizations {#Heading5}
Following is a quick tour of some of the many possible further
optimizations to Listing 37.1.
The run-handling loops could be unrolled more than the current two
times. However, bear in mind that a two-times unrolling gets more than
half the maximum unrolling benefit with less overhead than a more
heavily unrolled loop.
BX could be freed up in the Y-major code by breaking out separate loops
for X advances of 1 and -1. DX could be freed up by using AH as the
counter for the run loops, although this would limit the maximum line
length that could be handled. The freed registers could be used to keep
more of the whole-step and error variables in registers. Alternatively,
the freed registers could be used to implement more esoteric approaches
like unrolling the Y-major inner loop; such unrolling could take
advantage of the knowledge that only two run lengths are possible for
any given line. Strangely enough, on the 486 it might also be worth
unrolling the X-major inner loop, which consists of **REP STOSB**,
because of the slow start-up time of **REP** relative to the speed of
branching on that processor.
Special code could be implemented for lines with integral slopes,
because all runs are exactly the same length in such lines. Also, the
X-major code could try to write an aligned word at a time to display
memory whenever possible; this would improve the maximum possible
performance on some 16-bit VGAs.
One weakness of Listing 37.1 is that for lines with slopes between 0.5
and 2, the average run length is less than two, rendering run-length
slicing ineffective. This can be remedied by viewing lines in that range
as being composed of diagonal, rather than horizontal or vertical runs.
I haven't space to take this idea any further in this book, but it's not
very complicated, and it guarantees a minimum run length of 2, which
renders run drawing considerably more efficient, and makes techniques
such as unrolling the inner run-drawing loops more attractive.
Finally, be aware that run-length slice drawing is best for long lines,
because it has more and slower setup than a standard Bresenham's line
draw, including a divide. Run-length slice is great for 100-pixel lines,
but not necessarily for 20-pixel lines, and it's a sure thing that it's
not terrific for 3-pixel lines. Both approaches will work, but if
line-drawing performance is critical, whether you'll want to use
run-length slice or standard Bresenham's depends on the typical lengths
of the lines you'll be drawing. For lines of widely varying lengths, you
might want to implement both approaches, and choose the best one for
each line, depending on the line length—assuming, of course, that your
display memory is fast enough and your application demanding enough to
make that level of optimization worthwhile.
If your code looks broken from a performance perspective, think before
you fix it; that particular cat may be dead for a perfectly good reason.
I'll say it again: *Profile before you optimize*.