diff --git a/01-01.md b/01-01.md index c74e38d..d8181da 100644 --- a/01-01.md +++ b/01-01.md @@ -85,9 +85,10 @@ solid programming skills, preferably using an optimizing compiler or assembly language. The optimization at the end is just the finishing touch, however. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Without good design, good algorithms, and complete understanding of the program's operation, your carefully optimized code will amount to one of mankind's least fruitful creations—a fast slow program*. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Without good design, good algorithms, and complete understanding of the +> program's operation, your carefully optimized code will amount to one of +> mankind's least fruitful creations—a fast slow program. "What's a fast slow program?" you ask. That's a good question, and a brief (true) story is perhaps the best answer. diff --git a/01-02.md b/01-02.md index b0bbaac..00c4f2c 100644 --- a/01-02.md +++ b/01-02.md @@ -131,9 +131,9 @@ with optimization both on and off; all four times are pretty much the same, however, and all are much too slow to be acceptable. Listing 1.1 requires over two and one-half minutes to checksum *one* file! - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Listings 1.2 and 1.3 form the C/assembly equivalent to Listing 1.1, and Listings 1.6 and 1.7 form the C/assembly equivalent to Listing 1.5.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Listings 1.2 and 1.3 form the C/assembly equivalent to Listing 1.1, and +> Listings 1.6 and 1.7 form the C/assembly equivalent to Listing 1.5. These results make it clear that it's folly to rely on your compiler's optimization to make your programs fast. Listing 1.1 is simply poorly diff --git a/01-04.md b/01-04.md index ed8dba1..9530afa 100644 --- a/01-04.md +++ b/01-04.md @@ -10,9 +10,10 @@ chapter: '01' pages: 013-015 --- - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Make sure you understand what really goes on when you insert a seemingly-innocuous function call into the time-critical portions of your code.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Make sure you understand what really goes on when you insert a +> seemingly-innocuous function call into the time-critical portions of +> your code. In this case that means knowing how DOS and the C/C++ file-access libraries do their work. In other words, *know the territory*! @@ -74,9 +75,13 @@ that handle the time-critical operations, but C is still used for checking command-line parameters, operning files, printing, and the like. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *If you were to implement any of the listings in this chapter entirely in hand-optimized assembly, I suppose you might get a performance improvement of a few percent—but I rather doubt you'd get even that much, and you'd sure as heck spend an awful lot of time for whatever meager improvement does result. Let C do what it does well, and use assembly only when it makes a perceptible difference.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> If you were to implement any of the listings in this chapter entirely in +> hand-optimized assembly, I suppose you might get a performance +> improvement of a few percent—but I rather doubt you'd get even that +> much, and you'd sure as heck spend an awful lot of time for whatever +> meager improvement does result. Let C do what it does well, and use +> assembly only when it makes a perceptible difference. Besides, we don't want to optimize until the design is refined to our satisfaction, and that won't be the case until we've thought about other diff --git a/01-05.md b/01-05.md index ff96d3d..27ed749 100644 --- a/01-05.md +++ b/01-05.md @@ -21,9 +21,12 @@ Table 1.1 shows that Listing 1.5 is 2.5 to 4 times faster than Listing 1.4 (and as much as 49 times faster than Listing 1.1!), even though it uses no assembly at all. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Clearly, you can do well by using special-purpose C code in place of a C library function—if you have a thorough understanding of how the C library function operates and exactly what your application needs done. Otherwise, you'll end up rewriting C library functions in C, which makes no sense at all.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Clearly, you can do well by using special-purpose C code in place of a C +> library function—if you have a thorough understanding of how the C +> library function operates and exactly what your application needs done. +> Otherwise, you'll end up rewriting C library functions in C, which makes +> no sense at all. **LISTING 1.5 L1-5.C** diff --git a/01-06.md b/01-06.md index 28c7c6b..a27f073 100644 --- a/01-06.md +++ b/01-06.md @@ -81,9 +81,9 @@ instructions and/or unrolled loops can be used effectively, assembly tends to be considerably faster relative to C than it is in this very specific case. - ------------------- ----------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Don't get hung up on optimizing compilers or assembly language—the best optimizer is between your ears.* - ------------------- ----------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Don't get hung up on optimizing compilers or assembly language—the best +> optimizer is between your ears. All this is basically a way of saying: Know where you're going, know the territory, and know when it matters. diff --git a/02-01.md b/02-01.md index 346fb5a..fa33ac5 100644 --- a/02-01.md +++ b/02-01.md @@ -62,9 +62,9 @@ me—the code was rotating each bit into place separately, so that a multibit rotation was being performed every time through the loop, for a total of four separate time-consuming multibit rotations! - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *While the instructions themselves were individually optimized, the overall approach did not make the best possible use of the instructions.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> While the instructions themselves were individually optimized, the +> overall approach did not make the best possible use of the instructions. I changed the code to the following: diff --git a/02-02.md b/02-02.md index 7b857f7..38ac2ff 100644 --- a/02-02.md +++ b/02-02.md @@ -105,6 +105,6 @@ knowledge about programming them effectively is by far the hardest knowledge to gather. A good portion of this book is devoted to seeking out such knowledge. - ------------------- ------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Be forewarned, though: No matter how much you learn about programming the PC in assembly, there's always more to discover.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------ \ No newline at end of file +> ![](images/i.jpg) +> Be forewarned, though: No matter how much you learn about programming +> the PC in assembly, there's always more to discover. \ No newline at end of file diff --git a/02-03.md b/02-03.md index a9165e7..99ffcc9 100644 --- a/02-03.md +++ b/02-03.md @@ -43,9 +43,12 @@ reusability, source code control, choice of development environment, and the like that they often forget rule \#1: From the user's perspective, *performance is fundamental*. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Comment your code, design it carefully, and write non-time-critical portions in a high-level language, if you wish—but when you write the portions that interact with the user and/or affect response time, performance must be your paramount objective, and assembly is the path to that goal*. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Comment your code, design it carefully, and write non-time-critical +> portions in a high-level language, if you wish—but when you write the +> portions that interact with the user and/or affect response time, +> performance must be your paramount objective, and assembly is the path +> to that goal. Knowledge of the sort described earlier is absolutely essential to fulfilling either of the objectives of assembly programming. What that diff --git a/03-01.md b/03-01.md index 8fb5817..d4dabc1 100644 --- a/03-01.md +++ b/03-01.md @@ -57,9 +57,13 @@ performance of his code and found it to be unexpectedly slow, curiosity might well have led him to experiment further and thereby add to his store of reliable information about the CPU. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *There you have an important tenet of assembly language optimization: After crafting the best code possible, check it in action to see if it's really doing what you think it is. If it's not behaving as expected, that's all to the good, since solving mysteries is the path to knowledge. You'll learn more in this way, I assure you, than from any manual or book on assembly language.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> There you have an important tenet of assembly language optimization: +> After crafting the best code possible, check it in action to see if it's +> really doing what you think it is. If it's not behaving as expected, +> that's all to the good, since solving mysteries is the path to +> knowledge. You'll learn more in this way, I assure you, than from any +> manual or book on assembly language. *Assume nothing*. I cannot emphasize this strongly enough—when you care about performance, do your best to improve the code and then *measure* diff --git a/04-04.md b/04-04.md index 8ef2fee..c4fe635 100644 --- a/04-04.md +++ b/04-04.md @@ -38,9 +38,12 @@ depending on the code mix preceding that instruction.* Similarly, the state in which a given instruction leaves the prefetch queue affects the overall execution time of the following instructions. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In other words, while the execution time for a given instruction is constant, the fetch time for that instruction depends heavily on the context in which the instruction is executing—the amount of prefetching the preceding instructions allowed—and can vary from a full 4 cycles per instruction byte to no time at all.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> In other words, while the execution time for a given instruction is +> constant, the fetch time for that instruction depends heavily on the +> context in which the instruction is executing—the amount of prefetching +> the preceding instructions allowed—and can vary from a full 4 cycles per +> instruction byte to no time at all. As we'll see later, other cycle-eaters, such as DRAM refresh and display memory wait states, can cause prefetching variations even during diff --git a/04-05.md b/04-05.md index ed44020..0b10db9 100644 --- a/04-05.md +++ b/04-05.md @@ -83,9 +83,9 @@ in the Intel manuals, but may take as much as 4 cycles per byte longer, depending on the state of the prefetch queue when the preceding instruction ends. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The only true execution time for an instruction is a time measured in a certain context, and that time is meaningful only in that context.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The only true execution time for an instruction is a time measured in a +> certain context, and that time is meaningful only in that context. What we *really* want is to know how long useful working code takes to run, not how long a single instruction takes, and the Zen timer gives us diff --git a/04-10.md b/04-10.md index 086954c..cfdb150 100644 --- a/04-10.md +++ b/04-10.md @@ -10,9 +10,14 @@ chapter: '04' pages: 106-109 --- - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *A line-drawing subroutine, which executes perhaps a dozen instructions for each display memory access, generally loses less performance to the display adapter cycle-eater than does a block-copy or scrolling subroutine that uses **REP MOVS** instructions. Scaled and three-dimensional graphics, which spend a great deal of time performing calculations (often using very slow floating-point arithmetic), tend to suffer less.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> A line-drawing subroutine, which executes perhaps a dozen instructions +> for each display memory access, generally loses less performance to the +> display adapter cycle-eater than does a block-copy or scrolling +> subroutine that uses **REP MOVS** instructions. Scaled and +> three-dimensional graphics, which spend a great deal of time performing +> calculations (often using very slow floating-point arithmetic), tend to +> suffer less. In addition, code that accesses display memory infrequently tends to suffer only about half of the maximum display memory wait states, @@ -48,9 +53,10 @@ and alter the rest, and write the byte back to display memory. Half as many display memory accesses means half as many display memory wait states. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Moreover, 486s and Pentiums, as well as recent Super VGAs, employ write-caching schemes that make display memory writes considerably faster than display memory reads.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Moreover, 486s and Pentiums, as well as recent Super VGAs, employ +> write-caching schemes that make display memory writes considerably +> faster than display memory reads. Along the same line, the display adapter cycle-eater makes the popular exclusive-OR animation technique, which requires paired reads and writes diff --git a/05-01.md b/05-01.md index be498fe..1992ec1 100644 --- a/05-01.md +++ b/05-01.md @@ -58,9 +58,10 @@ the overhead of driving back and forth made for miserable performance. Renting a truck (the restartable block approach) would have required more effort and forethought, but would have paid off handsomely. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The easy, familiar approach often has nothing in its favor except that it requires less thinking; not a great virtue when writing high-performance code—or when moving.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The easy, familiar approach often has nothing in its favor except that +> it requires less thinking; not a great virtue when writing +> high-performance code—or when moving. And with that, let's look at a fairly complex application of restartable blocks. diff --git a/05-02.md b/05-02.md index 5ec12f2..98fa345 100644 --- a/05-02.md +++ b/05-02.md @@ -18,9 +18,11 @@ exactly how long our search buffer is. Even if a given **strstr()** implementation is well-written, its performance will suffer, at least for our application, from unnecessary overhead. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This illustrates why you shouldn't think of C/C++ library functions as black boxes; understand what they do and try to figure out how they do it, and relate that to their performance in the context you're interested in.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> This illustrates why you shouldn't think of C/C++ library functions as +> black boxes; understand what they do and try to figure out how they do +> it, and relate that to their performance in the context you're +> interested in. ### Brute-Force Techniques {#Heading5} diff --git a/05-05.md b/05-05.md index cfda90b..32d1671 100644 --- a/05-05.md +++ b/05-05.md @@ -22,9 +22,11 @@ And so we find, as we so often will, that optimization is definitely not a cut-and-dried matter, and that there is no such thing as a single "best" approach. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *You must know what your application will typically do, and you must know whether you're more concerned with average or worst-case performance before you can decide how best to speed up your program—and, indeed, whether speeding it up is worth doing at all.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> You must know what your application will typically do, and you must know +> whether you're more concerned with average or worst-case performance +> before you can decide how best to speed up your program—and, indeed, +> whether speeding it up is worth doing at all. By the way, don't think that just because very large block sizes don't much improve performance, it wasn't worth using restartable blocks in @@ -56,9 +58,11 @@ to the **read()** function, but a code profiler could be used to do the same thing much more easily), I found that the best code in the world wouldn't make much difference. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *When you try to speed up code, take a moment to identify the hot spots in your program so that you know where optimization is needed and whether it will make a significant difference before you invest your time.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> When you try to speed up code, take a moment to identify the hot spots +> in your program so that you know where optimization is needed and +> whether it will make a significant difference before you invest your +> time. As for restartable blocks: Here we tackled a considerably more complex application of restartable blocks than we did in Chapter 1—which turned diff --git a/06-02.md b/06-02.md index 07ef0f3..2696199 100644 --- a/06-02.md +++ b/06-02.md @@ -173,9 +173,11 @@ Multiplying a 32-bit value by a non-power-of-two multiplier in just 2 cycles is a pretty neat trick, even though it works only on a 386 or 486. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The full list of values that **LEA** can multiply a register by on a 386 or 486 is: 2, 3, 4, 5, 8, and 9. That list doesn't include every multiplier you might want, but it covers some commonly used ones, and the performance is hard to beat.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The full list of values that **LEA** can multiply a register by on a 386 +> or 486 is: 2, 3, 4, 5, 8, and 9. That list doesn't include every +> multiplier you might want, but it covers some commonly used ones, and +> the performance is hard to beat. I'd like to extend my thanks to Duane Strong of Metagraphics for his help in brainstorming uses for the 386 version of **LEA** and for diff --git a/07-01.md b/07-01.md index aeab8f9..446fe3e 100644 --- a/07-01.md +++ b/07-01.md @@ -68,9 +68,11 @@ algorithm selection and good design are fundamental to performance. The extra horsepower a superb assembly language implementation gives a program is worth bothering with only in the context of a good design. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Assembly language optimization is a small but crucial corner of the PC programming world. Use it sparingly and only within the framework of a good design—but ignore it and you may find various portions of your anatomy out in the cold.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Assembly language optimization is a small but crucial corner of the PC +> programming world. Use it sparingly and only within the framework of a +> good design—but ignore it and you may find various portions of your +> anatomy out in the cold. So, drawing fortitude from the knowledge that our quest is a pure and worthy one, let's resume our exploration of assembly language @@ -96,9 +98,11 @@ instinctively stuffs the loop count in CX and reaches for **LOOP** when setting up a loop. That's fine—**LOOP** does, of course, work as advertised—but there is one problem: - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *On half of the processors in the x86 family, **LOOP** is slower than **DEC CX** followed by **JNZ**. (Granted, **DEC CX/JNZ** isn't precisely equivalent to **LOOP,** because **DEC** alters the flags and LOOP doesn't, but in most situations they're comparable.)* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> On half of the processors in the x86 family, **LOOP** is slower than +> **DEC CX** followed by **JNZ**. (Granted, **DEC CX/JNZ** isn't precisely +> equivalent to **LOOP,** because **DEC** alters the flags and LOOP +> doesn't, but in most situations they're comparable.) How can this be? Don't ask me, ask Intel. On the 8088 and 80286, **LOOP** is indeed faster than **DEC CX/JNZ** by a cycle, and **LOOP** @@ -116,6 +120,8 @@ Pentium as well. (By the way, all this is not just theory; I've timed the relative performances of **LOOP** and **DEC CX/JNZ** on a cached 386, and LOOP really is slower.) - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Things are stranger still for **LOOP**'s relative **JCXZ,** which branches if and only if CX is zero. **JCXZ** is faster than **AND CX,CX/JZ** on the 8088 and 80286, and equivalent on the 80386—but is about twice as slow on the 486!* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +> ![](images/i.jpg) +> Things are stranger still for **LOOP**'s relative **JCXZ,** which +> branches if and only if CX is zero. **JCXZ** is faster than **AND +> CX,CX/JZ** on the 8088 and 80286, and equivalent on the 80386—but is +> about twice as slow on the 486! \ No newline at end of file diff --git a/07-02.md b/07-02.md index 05d8736..c2426f3 100644 --- a/07-02.md +++ b/07-02.md @@ -86,9 +86,12 @@ checking whether it's zero. There may be ways to perform those tasks a little faster by selecting different instructions, but they can get only so fast, and branching can't even get all that fast. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The trick, then, is not to find the fastest way to decrement a count and branch conditionally, but rather to figure out how to accomplish the same result without decrementing or branching as often. Remember the Kobiyashi Maru problem in* Star Trek*?The same principle applies here: Redefine the problem to one that offers better solutions.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The trick, then, is not to find the fastest way to decrement a count and +> branch conditionally, but rather to figure out how to accomplish the +> same result without decrementing or branching as often. Remember the +> Kobiyashi Maru problem in *Star Trek*? The same principle applies here: +> Redefine the problem to one that offers better solutions. Consider Listing 7.1, which searches a buffer until either the specified byte is found, a zero byte is found, or the specified number of diff --git a/07-04.md b/07-04.md index 7d399a6..f2df2f5 100644 --- a/07-04.md +++ b/07-04.md @@ -151,6 +151,11 @@ that algorithmic improvements can produce, but it can get you a critical 50 percent or 100 percent improvement when you've exhausted all other avenues. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The point is simply this: You can gain far more by stepping back a bit and thinking of the fastest overall way for the CPU to perform a task than you can by saving a cycle here or there using different instructions. Try to think at the level of sequences of instructions rather than individual instructions, and learn to treat x86 instructions as building blocks with unique characteristics rather than as instructions dedicated to specific tasks.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +> ![](images/i.jpg) +> The point is simply this: You can gain far more by stepping back a bit +> and thinking of the fastest overall way for the CPU to perform a task +> than you can by saving a cycle here or there using different +> instructions. Try to think at the level of sequences of instructions +> rather than individual instructions, and learn to treat x86 instructions +> as building blocks with unique characteristics rather than as +> instructions dedicated to specific tasks. diff --git a/07-05.md b/07-05.md index df828f3..80373ab 100644 --- a/07-05.md +++ b/07-05.md @@ -64,9 +64,13 @@ code snippet in Listing 7.4. BIT_PATTERN=BIT_PATTERN SHL 1 ENDM - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Besides illustrating the advantages of local optimization, this example also shows that it generally pays to precalculate results; this is often done at or before assembly time, but precalculated tables can also be built at run time. This is merely one aspect of a fundamental optimization rule: Move as much work as possible out of your critical code by whatever means necessary.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Besides illustrating the advantages of local optimization, this example +> also shows that it generally pays to precalculate results; this is often +> done at or before assembly time, but precalculated tables can also be +> built at run time. This is merely one aspect of a fundamental +> optimization rule: Move as much work as possible out of your critical +> code by whatever means necessary. #### NOT Flips Bits—Not Flags {#Heading9} @@ -84,9 +88,11 @@ flags.) NOT is often used for tasks, such as flipping masks, where there's no reason to test the state of the result, and in that context it can be handy to keep the flags unmodified for later testing. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Besides, if you want to **NOT** an operand and set the flags in the process, you can just **XOR** it with -1. Put another way, the only functional difference between **NOT AX** and **XOR AX,0FFFFH** is that **XOR** modifies the flags and **NOT** doesn't.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Besides, if you want to **NOT** an operand and set the flags in the +> process, you can just **XOR** it with -1. Put another way, the only +> functional difference between **NOT AX** and **XOR AX,0FFFFH** is that +> **XOR** modifies the flags and **NOT** doesn't. The x86 instruction set offers many ways to accomplish almost any task. Understanding the subtle distinctions between the instructions—whether @@ -152,9 +158,11 @@ altering the arithmetic flags is a common characteristic of program control instructions (as opposed to arithmetic and logical instructions like **SUB** and **AND,** which do alter the flags). - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *The rule is not that the arithmetic flags change whenever the CPU performs a calculation; rather, the flags change whenever you execute an arithmetic, logical, or flag control (such as **CLC** to clear the Carry flag) instruction.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> The rule is not that the arithmetic flags change whenever the CPU +> performs a calculation; rather, the flags change whenever you execute an +> arithmetic, logical, or flag control (such as **CLC** to clear the Carry +> flag) instruction. Not only do **LOOP** and **JCXZ** not alter the flags, but **REP MOVS**, which counts down CX to 0, doesn't affect the flags either. diff --git a/08-01.md b/08-01.md index d6a727a..0cd1bdc 100644 --- a/08-01.md +++ b/08-01.md @@ -50,9 +50,12 @@ C/C++ and Watcom C are by now pretty good at fine-tuning C code, and you're not likely to do much better by taking the compiler's assembly language output and tweaking it. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *To make the process of translating C code to assembly language worth the trouble, you must ignore what the compiler does and design your assembly language code from a pure assembly language perspective. With a merely adequate translation, you risk laboring mightily for little or no reward.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> To make the process of translating C code to assembly language worth the +> trouble, you must ignore what the compiler does and design your assembly +> language code from a pure assembly language perspective. With a merely +> adequate translation, you risk laboring mightily for little or no +> reward. Apropos of which, when was the last time you heard of Terry Jacks? @@ -88,9 +91,13 @@ Also make it a point to concentrate on refining your program design and algorithmic approach at the conceptual and/or C levels before doing any assembly language optimization. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Assembly language optimization is the final and far from the only step in the optimization chain, and as such should be performed last; converting to assembly too soon can lock in your code before the design is optimal. At the very least, conversion to assembly tends to make future changes and debugging more difficult, slowing you down and limiting your options.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Assembly language optimization is the final and far from the only step +> in the optimization chain, and as such should be performed last; +> converting to assembly too soon can lock in your code before the design +> is optimal. At the very least, conversion to assembly tends to make +> future changes and debugging more difficult, slowing you down and +> limiting your options. ### Don't Call Your Functions on Me, Baby {#Heading4} diff --git a/08-02.md b/08-02.md index bbc98a9..a4d7451 100644 --- a/08-02.md +++ b/08-02.md @@ -37,9 +37,11 @@ for the duration of the loop. But two far pointers used in the same loop confuse every compiler I've seen, causing the full segment:offset address to be reloaded each time either pointer is used. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *This particularly affects performance in 286 protected mode (under OS/2 1.X or the Rational DOS Extender, for example) because segment loads in protected mode take a minimum of 17 cycles, versus a mere 2 cycles in real mode.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> This particularly affects performance in 286 protected mode (under OS/2 +> 1.X or the Rational DOS Extender, for example) because segment loads in +> protected mode take a minimum of 17 cycles, versus a mere 2 cycles in +> real mode. In assembly language you have full control over segments. Use it, and, if necessary, reorganize your code to minimize segment loading. @@ -61,9 +63,11 @@ Am I saying that C compilers produce better code than you do? No, I'm saying that they *can,* unless you use assembly language properly. Writing code in assembly language rather than C guarantees nothing. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *You can write good assembly, bad assembly, or assembly that is virtually indistinguishable from compiled code; you are more likely than not to write the latter if you think that optimization consists of tweaking compiled C code.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> You can write good assembly, bad assembly, or assembly that is virtually +> indistinguishable from compiled code; you are more likely than not to +> write the latter if you think that optimization consists of tweaking +> compiled C code. Sure, you can probably use the registers more efficiently and take advantage of an instruction or two that the compiler missed, but the @@ -109,9 +113,12 @@ enough, there's no reason why you can't reorganize the data. This might mean removing the array elements from the structures and storing them in their own array so that **REP SCASW** *could* be used. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Organizing a program's data so that the performance of the critical sections can be optimized is a key part of design, and one that's easily shortchanged unless, during the design stage, you thoroughly understand and work to bring together your data needs, the critical sections of your program, and potential assembly language optimizations.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Organizing a program's data so that the performance of the critical +> sections can be optimized is a key part of design, and one that's easily +> shortchanged unless, during the design stage, you thoroughly understand +> and work to bring together your data needs, the critical sections of +> your program, and potential assembly language optimizations. More on this shortly. diff --git a/09-02.md b/09-02.md index 79bf892..6963a62 100644 --- a/09-02.md +++ b/09-02.md @@ -68,9 +68,13 @@ now on) by arranging your operands so that the multiplier (always the rightmost operand following **MUL** or **IMUL**) is no larger than the other operand. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Why? Because the 386 processes one multiplier bit per cycle and immediately ends a multiplication when all significant bits of the multiplier have been processed, so fewer cycles are required to multiply a large multiplicand times a small multiplier than a small multiplicand times a large multiplier, by a factor of about 1 cycle for each significant multiplier bit eliminated.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Why? Because the 386 processes one multiplier bit per cycle and +> immediately ends a multiplication when all significant bits of the +> multiplier have been processed, so fewer cycles are required to multiply +> a large multiplicand times a small multiplier than a small multiplicand +> times a large multiplier, by a factor of about 1 cycle for each +> significant multiplier bit eliminated. (There's a minimum execution time on this trick; below 3 significant multiplier bits, no additional cycles are saved.) For example, @@ -89,9 +93,11 @@ This highlights another interesting point: **MUL** and **IMUL** on the 386 are so fast that alternative multiplication approaches, while generally still faster, are worthwhile only in truly time-critical code. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *On 386SXs and uncached 386s, where code size can significantly affect performance due to instruction prefetching, the compact **MUL** and **IMUL** instructions can approach and in some cases even outperform the "optimized" alternatives.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> On 386SXs and uncached 386s, where code size can significantly affect +> performance due to instruction prefetching, the compact **MUL** and +> **IMUL** instructions can approach and in some cases even outperform the +> "optimized" alternatives. All in all, **MUL** and **IMUL** are reasonable performers on the 386, no longer to be avoided in most cases—and you can help that along by @@ -127,9 +133,10 @@ deal is handling **REPNZ SCASB** matches, which require checking the remainder of the string with **REPZ CMPS** and restarting **REPNZ SCASB** if no match is found. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Rob points out that the number of **REPNZ SCASB** matches can easily be reduced simply by scanning for the character in the searched-for string that appears least often in the buffer being searched.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Rob points out that the number of **REPNZ SCASB** matches can easily be +> reduced simply by scanning for the character in the searched-for string +> that appears least often in the buffer being searched. Imagine, if you will, that you're searching for the string "EQUAL." By my approach, you'd use **REPNZ SCASB** to scan for each occurrence of diff --git a/09-03.md b/09-03.md index 74156ee..0bbbd19 100644 --- a/09-03.md +++ b/09-03.md @@ -31,9 +31,10 @@ counting. Listings 9.1 and 9.2 use pretty much the same instructions, and even use the same approach of scanning with **REPNZ SCASB** and using **REPZ CMPS** to check scanning matches. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The difference between Listings 9.1 and 9.2 (which gives you more than a doubling of performance) is due entirely to understanding the nature of the data being handled, and biasing the code to reflect that knowledge.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The difference between Listings 9.1 and 9.2 (which gives you more than a +> doubling of performance) is due entirely to understanding the nature of +> the data being handled, and biasing the code to reflect that knowledge. ![**Figure 9.2**  *Faster searching method for locating a text string.*](images/09-02.jpg) diff --git a/09-06.md b/09-06.md index a415b1f..1d09873 100644 --- a/09-06.md +++ b/09-06.md @@ -127,9 +127,9 @@ slows performance because instructions must be fetched through the half-pint 16-bit bus; on a 386, the effect depends on the instruction mix and whether there's a cache. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *On balance, though, it's as important to keep your most-used variables in the stack's sweet spot in 386 native mode as it was on the 8088.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> On balance, though, it's as important to keep your most-used variables +> in the stack's sweet spot in 386 native mode as it was on the 8088. In assembly, it's easy to control the organization of your stack frame. In C, however, you'll have to figure out the allocation scheme your diff --git a/10-01.md b/10-01.md index 345b128..2dba6e2 100644 --- a/10-01.md +++ b/10-01.md @@ -67,9 +67,12 @@ always hungered to get results from my work as soon as possible; I gravitated toward graphics for its instant and very visible gratification. Over time, however, I've learned patience. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *I've come to spend an increasingly large portion of my time choosing algorithms, designing, and simply giving my mind quiet time in which to work on problems and come up with non-obvious approaches before coding; and I've found that the extra time up front more than pays for itself in both decreased coding time and superior programs.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> I've come to spend an increasingly large portion of my time choosing +> algorithms, designing, and simply giving my mind quiet time in which to +> work on problems and come up with non-obvious approaches before coding; +> and I've found that the extra time up front more than pays for itself in +> both decreased coding time and superior programs. In this chapter, I'm going to walk you through a simple but illustrative case history that nicely points up the wisdom of delaying gratification diff --git a/10-03.md b/10-03.md index f8b8001..52c1a55 100644 --- a/10-03.md +++ b/10-03.md @@ -93,9 +93,13 @@ As you can see from Table 10.1, Euclid's algorithm is superior, especially for large numbers (and imagine if we were working with large *longs!*). - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Had I been implementing GCD determination without Sedgewick's help, I would surely not have settled for Listing 10.1—but I might well have ended up with Listing 10.2 in my enthusiasm over the "brilliant" discovery of subtracting the lesser Using Euclid's algorithm to find a GCD number from the greater. In a commercial product, my lack of patience and discipline could have been costly indeed.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Had I been implementing GCD determination without Sedgewick's help, I +> would surely not have settled for Listing 10.1—but I might well have +> ended up with Listing 10.2 in my enthusiasm over the "brilliant" +> discovery of subtracting the lesser Using Euclid's algorithm to find a +> GCD number from the greater. In a commercial product, my lack of +> patience and discipline could have been costly indeed. ![**Figure 10.3**  *Using Euclid's algorithm to find a GCD.*](images/10-03.jpg) diff --git a/10-04.md b/10-04.md index f4bf54c..4d13589 100644 --- a/10-04.md +++ b/10-04.md @@ -106,9 +106,11 @@ lousy at; compilers could out-optimize you at this level with one pass tied behind their back *if* they knew as much about the code you're writing as you do, which they don't. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Design optimization—conceptual breakthroughs in understanding the relationships between the needs of an application, the nature of the data the application works with, and what the computer can do—is global pattern matching.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Design optimization—conceptual breakthroughs in understanding the +> relationships between the needs of an application, the nature of the +> data the application works with, and what the computer can do—is global +> pattern matching. Computers are *much* worse at that sort of pattern matching than humans; computers have no way to integrate vast amounts of disparate diff --git a/11-02.md b/11-02.md index 9fc4ff2..a0f19a3 100644 --- a/11-02.md +++ b/11-02.md @@ -73,9 +73,11 @@ 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. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *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.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> 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. diff --git a/11-03.md b/11-03.md index 6c69f67..551ebe8 100644 --- a/11-03.md +++ b/11-03.md @@ -69,9 +69,9 @@ The penalty for performing a word-sized access starting at an odd address is easy to calculate: Two accesses take twice as long as one access. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In other words, the effective capacity of the 286's external data bus is* *halved* *when a word-sized access to an odd address is performed.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> In other words, the effective capacity of the 286's external data bus +> is *halved* when a word-sized access to an odd address is performed. That, in a nutshell, is the data alignment cycle-eater, the one new cycle-eater of the 286 and 386. (The data alignment cycle-eater is a diff --git a/11-04.md b/11-04.md index e32c274..56d61c3 100644 --- a/11-04.md +++ b/11-04.md @@ -53,9 +53,9 @@ instruction if necessary, and the time required to execute a **NOP** can sometimes cancel the performance advantage of having a word-aligned branch destination. - ------------------- ----------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Consequently, it's best to word-align only those branch destinations that can be reached solely by branching.* - ------------------- ----------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Consequently, it's best to word-align only those branch destinations +> that can be reached solely by branching. I recommend that you only go out of your way to word-align the start offsets of your subroutines, as in: @@ -112,9 +112,8 @@ interrupt occurring between the two instructions will be serviced more slowly than it normally would. The same goes for decrementing twice; use **SUB SP,2** instead. - ------------------- ------------------------------------------------ - ![](images/i.jpg) *Keep the stack pointer aligned at all times.* - ------------------- ------------------------------------------------ +> ![](images/i.jpg) +> Keep the stack pointer aligned at all times. #### The DRAM Refresh Cycle-Eater: Still an Act of God {#Heading11 align="center"} diff --git a/11-05.md b/11-05.md index c0ca2d5..dec3d9b 100644 --- a/11-05.md +++ b/11-05.md @@ -61,9 +61,12 @@ adapter cycle-eater, as measured in cycles lost to wait states, is indeed much worse on AT-class computers than it is on the PC, and it's worse still on more powerful computers. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *How bad is the display adapter cycle-eater on an AT? It's this bad: Based on my (not inconsiderable) experience in timing display adapter access, I've found that the display adapter cycle-eater can slow an AT—or even a 386 computer—to near-PC speeds when display memory is accessed.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> How bad is the display adapter cycle-eater on an AT? It's this bad: +> Based on my (not inconsiderable) experience in timing display adapter +> access, I've found that the display adapter cycle-eater can slow an +> AT—or even a 386 computer—to near-PC speeds when display memory is +> accessed. I know that's hard to believe, but the display adapter cycle-eater gives out just so many display memory accesses in a given time, and no more, diff --git a/11-06.md b/11-06.md index 9585df6..dc765fb 100644 --- a/11-06.md +++ b/11-06.md @@ -21,9 +21,10 @@ to address a full 4 gigabytes per segment, but in real mode you're still limited to 64K, even with 32-bit registers and the new addressing modes, unless you play some unorthodox tricks with the segment registers. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Note well: Those tricks don't necessarily work with system software such as Windows, so I'd recommend against using them. If you want 4-gigabyte segments, use a 32-bit environment such as Win32.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Note well: Those tricks don't necessarily work with system software such +> as Windows, so I'd recommend against using them. If you want 4-gigabyte +> segments, use a 32-bit environment such as Win32. #### Optimization Rules: The More Things Change... {#Heading15 align="center"} diff --git a/12-01.md b/12-01.md index 9f36a3f..40d4a93 100644 --- a/12-01.md +++ b/12-01.md @@ -84,9 +84,14 @@ for earlier x86 processors. Generally, I'll be discussing optimization for real mode (it being the most widely used mode at the moment), although many of the rules should apply to protected mode as well. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *486 optimization is generally more precise and less frustrating than optimization for other x86 processors because every 486 has an identical internal cache. Whenever both the instructions being executed and the data the instructions access are in the cache, those instructions will run in a consistent and calculatable number of cycles on all 486s, with little chance of interference from the prefetch queue and without regard to the speed of external memory.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> 486 optimization is generally more precise and less frustrating than +> optimization for other x86 processors because every 486 has an identical +> internal cache. Whenever both the instructions being executed and the +> data the instructions access are in the cache, those instructions will +> run in a consistent and calculatable number of cycles on all 486s, with +> little chance of interference from the prefetch queue and without regard +> to the speed of external memory. In other words, for cached code (which time-critical code almost always is), performance is predictable and can be calculated with good @@ -115,9 +120,11 @@ matter what register is used.) **MOV AX, [BX+DI]** and **MOV CL, [BP+SI+10]** perform indexed addressing; **MOV AX,[BX]** and **MOV DL, [SI+1]** do not. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Therefore, in real mode, the rule is to avoid using two registers to point to memory whenever possible. Often, this simply means adding the two registers together outside a loop before memory is actually addressed.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Therefore, in real mode, the rule is to avoid using two registers to +> point to memory whenever possible. Often, this simply means adding the +> two registers together outside a loop before memory is actually +> addressed. As an example, you might adhere to this rule by replacing the code diff --git a/12-02.md b/12-02.md index 00a45d2..fcc55ce 100644 --- a/12-02.md +++ b/12-02.md @@ -88,9 +88,10 @@ documentation understates the extent of the penalty for interrupting the address calculation pipeline by loading a memory pointer just before it's used. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *The truth of the matter appears to be that if a register is the destination of one instruction and is then used by the next instruction to address memory in real mode, not one but two cycles are lost!* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> The truth of the matter appears to be that if a register is the +> destination of one instruction and is then used by the next instruction +> to address memory in real mode, not one but two cycles are lost! In 32-bit protected mode, however, the penalty is, in fact, the 1 cycle that Intel . diff --git a/12-03.md b/12-03.md index d81c632..bbc991a 100644 --- a/12-03.md +++ b/12-03.md @@ -146,9 +146,10 @@ mentioned in this chapter apply to **XLAT**, apparently because **XLAT** is so slow—4 cycles—that it gives the 486 time to perform addressing calculations during the course of the instruction. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *While it's nice that **XLAT** doesn't suffer from the various 486 addressing penalties, the reason for that is basically that **XLAT** is slow, so there's still no compelling reason to use **XLAT** on the 486.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> While it's nice that **XLAT** doesn't suffer from the various 486 +> addressing penalties, the reason for that is basically that **XLAT** is +> slow, so there's still no compelling reason to use **XLAT** on the 486. In general, penalties for interrupting the 486's pipeline apply primarily to the fast core instructions of the 486, most notably diff --git a/12-04.md b/12-04.md index 0ec3de4..0b47ff0 100644 --- a/12-04.md +++ b/12-04.md @@ -75,9 +75,11 @@ presumably, **MOV CL,AL** interrupts the pipeline again because the pipeline is now on its first cycle: the one that loading a byte register can affect. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *I know—it seems awfully complicated. It isn't, really. Generally, try not to use byte destinations exactly two cycles before using a register to address memory, and try not to load a register either one or two cycles before using it to address memory, and you'll be fine.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> I know—it seems awfully complicated. It isn't, really. Generally, try +> not to use byte destinations exactly two cycles before using a register +> to address memory, and try not to load a register either one or two +> cycles before using it to address memory, and you'll be fine. #### Timing Your Own 486 Code {#Heading11} @@ -143,9 +145,9 @@ more than doubles to 224 µs, or 3.7 cycles per repetition; the extra seven-tenths of a cycle comes from fetching non-cached instruction bytes. - ------------------- ----------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Whenever you see non-integral timing results of this sort, it's a good bet that the test code or data isn't cached.* - ------------------- ----------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Whenever you see non-integral timing results of this sort, it's a good +> bet that the test code or data isn't cached. ### The Story Continues {#Heading12} diff --git a/13-01.md b/13-01.md index 32a4444..b4bd0af 100644 --- a/13-01.md +++ b/13-01.md @@ -106,9 +106,10 @@ the intervening instruction takes two cycles, there's no penalty at all. ![**Figure 13.1**  *Cycle-eaters in the original WC.*](images/13-01.jpg) - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Remember, pipeline penalties diminish with increasing number of cycles, not instructions, between the pipeline disrupter and the potentially affected instruction.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Remember, pipeline penalties diminish with increasing number of cycles, +> not instructions, between the pipeline disrupter and the potentially +> affected instruction. **LISTING 13.2 L13-2.ASM** diff --git a/13-03.md b/13-03.md index 101a77c..f91ede5 100644 --- a/13-03.md +++ b/13-03.md @@ -68,9 +68,13 @@ operations include all the string instructions except **REP MOVS,** as well as **XLAT, LOOP,** and, of course, **PUSH *mem*** and **POP *mem.*** - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Whenever possible, try to use the 486's 1-cycle instructions, including **MOV, ADD, SUB, CMP, ADC, SBB, XOR, AND, OR, TEST, LEA**, and **PUSH reg** and **POP reg**. These instructions have an added benefit in that it's often possible to rearrange them for maximum pipeline efficiency, as is the case with Terje's optimization described earlier in this chapter.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Whenever possible, try to use the 486's 1-cycle instructions, including +> **MOV, ADD, SUB, CMP, ADC, SBB, XOR, AND, OR, TEST, LEA**, and **PUSH +> reg** and **POP reg**. These instructions have an added benefit in that +> it's often possible to rearrange them for maximum pipeline efficiency, +> as is the case with Terje's optimization described earlier in this +> chapter. ### Optimal 1-Bit Shifts and Rotates {#Heading6} diff --git a/13-04.md b/13-04.md index 5fa90d9..67ae17d 100644 --- a/13-04.md +++ b/13-04.md @@ -63,9 +63,12 @@ example, **MOV AL, [EBX]** is a 2-byte instruction; **MOV AL, [EBX+10H]** is a 3-byte instruction; and **MOV AL, [EBX+10000H]** is a 6-byte instruction. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Note that 1 and 4-byte displacements, but not 2-byte displacements, are supported for 32-bit addressing. Code size can be greatly improved by keeping stack frame variables within 128 bytes of EBP, and variables in pointed-to structures within 127 bytes of the start of the structure, so that displacements can be 1 rather than 4 bytes.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Note that 1 and 4-byte displacements, but not 2-byte displacements, are +> supported for 32-bit addressing. Code size can be greatly improved by +> keeping stack frame variables within 128 bytes of EBP, and variables in +> pointed-to structures within 127 bytes of the start of the structure, so +> that displacements can be 1 rather than 4 bytes. However, because 32-bit addressing supports many more addressing combinations than 16-bit addressing, the Mod-R/M byte can't describe all diff --git a/14-01.md b/14-01.md index e2dbf4c..a8e8c33 100644 --- a/14-01.md +++ b/14-01.md @@ -100,9 +100,10 @@ It's important to understand that we're assuming that the buffer is typical text. That's what I meant at the outset, when I said that the information you need may be under your nose. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Formally, you don't know a blessed thing about the search buffer, but experience, common sense, and your knowledge of the application give you a great deal of useful, if somewhat imprecise, information.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Formally, you don't know a blessed thing about the search buffer, but +> experience, common sense, and your knowledge of the application give you +> a great deal of useful, if somewhat imprecise, information. If the buffer contains the letter ‘A' repeated 1,000 times, followed by the letter ‘B,' then the **REPNZ SCASB/REPZ CMPS** approach will be much diff --git a/14-02.md b/14-02.md index 3d1994d..f62a840 100644 --- a/14-02.md +++ b/14-02.md @@ -54,9 +54,10 @@ of bytes (a portion of the buffer); it doesn't matter in the least in what order we compare them, so long as all the bytes in one set are compared to the corresponding bytes in the other set. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Why on earth would we want to start with the rightmost character? Because a mismatch on the rightmost character tells us a great deal more than a mismatch on the leftmost character.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Why on earth would we want to start with the rightmost character? +> Because a mismatch on the rightmost character tells us a great deal more +> than a mismatch on the leftmost character. We learn nothing new from a mismatch on the leftmost character, except that the pattern can't match starting at that location. A mismatch on diff --git a/15-02.md b/15-02.md index 8caa01b..a64972a 100644 --- a/15-02.md +++ b/15-02.md @@ -96,9 +96,11 @@ have learned it by spending five minutes with Sedgewick's book. ![**Figure 15.2**  *Using a dummy head and tail node with a linked list.*](images/15-02.jpg) - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The next-node pointer of the head node, which points to the first real node, is the only part of the head node that's actually used. This way the same code works on the head node as on the rest of the list, so there are no special cases.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The next-node pointer of the head node, which points to the first real +> node, is the only part of the head node that's actually used. This way +> the same code works on the head node as on the rest of the list, so +> there are no special cases. Likewise, there should be a separate node for the tail of the list, so that every node that contains real data is guaranteed to have a node on diff --git a/16-03.md b/16-03.md index 9431d64..15abd41 100644 --- a/16-03.md +++ b/16-03.md @@ -142,9 +142,8 @@ One possible optimization is unrolling the loop, although that is truly a last resort because it tends to make further changes extremely difficult. - ------------------- ----------------------------------------------------------- - ![](images/i.jpg) *Exhaust all other optimizations before unrolling loops.* - ------------------- ----------------------------------------------------------- +> ![](images/i.jpg) +> Exhaust all other optimizations before unrolling loops. ### Challenges and Hazards {#Heading5} diff --git a/16-04.md b/16-04.md index 094c435..a2defab 100644 --- a/16-04.md +++ b/16-04.md @@ -52,9 +52,10 @@ base+index addressing (**[EBX+EAX]**), which saves a cycle on the 386. Second: Changing the instruction to **CMP [EAX],DH** saved 2 cycles—just enough, by good fortune, to speed up the whole program by 5 percent. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) ***CMP reg,[mem]** takes 6 cycles on the 386, but **CMP [ mem ],reg** takes only 5 cycles; you should always perform**CMP** with the memory operand on the left on the 386.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> **CMP reg,[mem]** takes 6 cycles on the 386, but **CMP [ mem ],reg** +> takes only 5 cycles; you should always perform**CMP** with the memory +> operand on the left on the 386. (Granted, **CMP [*mem*],*reg*** is 1 cycle slower than **CMP *reg*,[*mem*]** on the 286, and they're both the same on the 8088; in @@ -99,9 +100,10 @@ faster. Consider the incongruity of Terje's willingness to consider a 5 percent speedup significant in light of his later near-doubling of performance. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Don't get stuck in the rut of instruction-by-instruction optimization. It's useful in key loops, but very often, a change in approach will work far greater wonders than any amount of cycle counting can.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Don't get stuck in the rut of instruction-by-instruction optimization. +> It's useful in key loops, but very often, a change in approach will work +> far greater wonders than any amount of cycle counting can. By the way, Terje's WC50 program is a full-fledged counting program; it counts characters, words, and lines, can handle multiple files, and lets diff --git a/16-06.md b/16-06.md index b430b39..13fb74c 100644 --- a/16-06.md +++ b/16-06.md @@ -22,9 +22,11 @@ don't even guarantee that David Stafford's entry works perfectly, although, knowing him, it probably does; the more complex and cryptic the code, the greater the chance for obscure bugs.) - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Remember, optimize only when needed, and stop when further optimization will not be noticed. Optimization that's not perceptible to the user is like buying Telly Savalas a comb; it's not going to do any harm, but it's nonetheless a waste of time.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Remember, optimize only when needed, and stop when further optimization +> will not be noticed. Optimization that's not perceptible to the user is +> like buying Telly Savalas a comb; it's not going to do any harm, but +> it's nonetheless a waste of time. #### Optimization Level 1: Good Code {#Heading10} diff --git a/17-03.md b/17-03.md index 96a0050..063d073 100644 --- a/17-03.md +++ b/17-03.md @@ -58,9 +58,13 @@ state of our Game of Life implementation, the only areas worth looking at for possible optimizations are **cell\_state()** and **next\_generation().** - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *It's worth noting, though, that one reason **draw\_pixel()** doesn't much affect performance is that in Listing 17.1, we're smart enough to redraw pixels only when their states change, rather than during every generation. Detecting and eliminating redundant operations is part of knowing the nature of your data, and is a potent optimization technique that will be extremely useful a little later in this chapter.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> It's worth noting, though, that one reason **draw\_pixel()** doesn't +> much affect performance is that in Listing 17.1, we're smart enough to +> redraw pixels only when their states change, rather than during every +> generation. Detecting and eliminating redundant operations is part of +> knowing the nature of your data, and is a potent optimization technique +> that will be extremely useful a little later in this chapter. ### The Hazards and Advantages of Abstraction {#Heading6} @@ -81,9 +85,11 @@ know the cell storage format! This is a wonderful thing, in general; it saves programming time and bugs, and frees you to work on the application's needs, rather than implementation details. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *However, if you never look beneath the surface of the abstract model at the implementation details, you have no idea of what the true performance cost of various operations* *is, and, without that, you have largely surrendered control over performance.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> However, if you never look beneath the surface of the abstract model at +> the implementation details, you have no idea of what the true performance +> cost of various operations is, and, without that, you have largely +> surrendered control over performance. Having said that, let me hasten to add that algorithmic improvements can make a big difference even when working at a purely abstract level. For diff --git a/17-05.md b/17-05.md index d06ce44..0bc2f51 100644 --- a/17-05.md +++ b/17-05.md @@ -159,6 +159,7 @@ It's undoubtedly possible to improve the performance of Listing 17.4 further by fine-tuning the code, but no tremendous improvement is possible that way. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Once you've reached the point of fine-tuning pointer usage and register variables and the like in C or C++, you've become compiler-dependent; you therefore might as well go to assembly and get the real McCoy.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +> ![](images/i.jpg) +> Once you've reached the point of fine-tuning pointer usage and register +> variables and the like in C or C++, you've become compiler-dependent; +> you therefore might as well go to assembly and get the real McCoy. \ No newline at end of file diff --git a/17-06.md b/17-06.md index e18f069..45440c5 100644 --- a/17-06.md +++ b/17-06.md @@ -65,9 +65,12 @@ possible. Large lookup tables, oddly encoded cellmaps, and lots of bit-twiddling assembly code spring to mind as possible approaches. Can't you just feel your adrenaline start to pump? - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Relax. Step back. Try to divine the true nature of the problem. The object is not to count neighbors and check cell states as quickly as possible; that's just one possible implementation. The object is to determine when a cell's state must be changed and to change it appropriately, and that's what we need to do as quickly as possible.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Relax. Step back. Try to divine the true nature of the problem. The +> object is not to count neighbors and check cell states as quickly as +> possible; that's just one possible implementation. The object is to +> determine when a cell's state must be changed and to change it +> appropriately, and that's what we need to do as quickly as possible. What difference does that new perspective make? Let's approach it this way. What does a typical cellmap look like? As it happens, after a few diff --git a/17-08.md b/17-08.md index 31a9430..c63666f 100644 --- a/17-08.md +++ b/17-08.md @@ -41,9 +41,14 @@ well-optimized, and, as I noted, the program must be compiled with the large model for large cellmaps. Also, of course, the entire program is still in C++; note well that there's not a whit of assembly here. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *We've gotten more than a 30-times speedup simply by removing a little of the abstraction that C++ encourages, and by storing and processing the data in a manner appropriate for the typical nature of the data itself. In other words, we've done some linear, left-brained optimization (using pointers and reducing calls) and some non-linear, right-brained optimization (understanding the real problem and listening for the creative whisper of non-obvious solutions).* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> We've gotten more than a 30-times speedup simply by removing a little of +> the abstraction that C++ encourages, and by storing and processing the +> data in a manner appropriate for the typical nature of the data itself. +> In other words, we've done some linear, left-brained optimization (using +> pointers and reducing calls) and some non-linear, right-brained +> optimization (understanding the real problem and listening for the +> creative whisper of non-obvious solutions). No doubt we could get another two to five times improvement with good assembly code—but that's dwarfed by a 30-times improvement, so diff --git a/18-01.md b/18-01.md index 8671662..e5b7778 100644 --- a/18-01.md +++ b/18-01.md @@ -38,9 +38,18 @@ circumscribed arena. Why am I telling you this? First, because it is a useful lesson for programming. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *All programming is performed within limitations, some of which can be bent or changed, but many of which cannot. You cannot change the maximum memory bandwidth of a VGA, or the maximum instruction execution rate of a 486. That is why the stunning 3D demos you see at SIGGRAPH have only passing relevance to everyday life on the desktop. A rule that Intel's chip designers cannot break is 8086 compatibility, much as I'm sure they'd like to, but of course the flip side is that although RISC chips are technically superior, they command but a small fraction of the market; raw performance is not the arena of competition. Similarly, you will often be unable to change the specifications for the software you implement.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> All programming is performed within limitations, some of which can be +> bent or changed, but many of which cannot. You cannot change the maximum +> memory bandwidth of a VGA, or the maximum instruction execution rate of +> a 486. That is why the stunning 3D demos you see at SIGGRAPH have only +> passing relevance to everyday life on the desktop. A rule that Intel's +> chip designers cannot break is 8086 compatibility, much as I'm sure +> they'd like to, but of course the flip side is that although RISC chips +> are technically superior, they command but a small fraction of the +> market; raw performance is not the arena of competition. Similarly, you +> will often be unable to change the specifications for the software you +> implement. ### Breaking the Rules {#Heading3} diff --git a/19-02.md b/19-02.md index 56c2a6c..f76eed1 100644 --- a/19-02.md +++ b/19-02.md @@ -10,9 +10,11 @@ chapter: '19' pages: 373-375 --- - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *The Pentium, on the other hand, has two separate 8K caches, one for code and one for data, so code prefetches can never collide with data fetches; the prefetch queue can stall only when the code being fetched isn't in the internal code cache.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> The Pentium, on the other hand, has two separate 8K caches, one for code +> and one for data, so code prefetches can never collide with data +> fetches; the prefetch queue can stall only when the code being fetched +> isn't in the internal code cache. (And yes, self-modifying code still works; as with all Pentium changes, the dual caches introduce no incompatibilities with 386/486 code.) Also, @@ -40,9 +42,12 @@ bits, and runs twice as fast, at 66 MHz, so the Pentium can fetch both instruction and data bytes from the external cache four times as fast as the 486. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Even when the Pentium is running flat-out with both pipes in use, it can generally consume only about twice as many bytes as the 486; so the ratio of external memory bandwidth to processing power is much improved, although real-world performance is heavily dependent on the size and speed of the external cache.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Even when the Pentium is running flat-out with both pipes in use, it can +> generally consume only about twice as many bytes as the 486; so the +> ratio of external memory bandwidth to processing power is much improved, +> although real-world performance is heavily dependent on the size and +> speed of the external cache. The upshot of all this is that at the same clock speed, with code and data that are mostly in the internal caches, the Pentium maxes out diff --git a/19-03.md b/19-03.md index 98dfbf5..fb01f46 100644 --- a/19-03.md +++ b/19-03.md @@ -49,9 +49,13 @@ recommends for 486 jump targets. The 32-byte alignment might make for slightly more efficient Pentium cache usage, but would make code much bigger overall. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In fact, given that most jump targets aren't in performance-critical code, it's hard to make a compelling argument for aligning branch targets even on the 486. I'd say that no alignment (except possibly where you know a branch target lies in a key loop), or at most dword alignment (for the 386) is plenty, and can shrink code size considerably.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> In fact, given that most jump targets aren't in performance-critical +> code, it's hard to make a compelling argument for aligning branch +> targets even on the 486. I'd say that no alignment (except possibly +> where you know a branch target lies in a key loop), or at most dword +> alignment (for the 386) is plenty, and can shrink code size +> considerably. Instruction prefixes are awfully expensive; avoid them if you can. (These include size and addressing prefixes, segment overrides, @@ -85,9 +89,15 @@ primarily an operating system instruction—but there's a hidden gotcha here because the **XCHG** instruction always locks the bus when used with a memory operand. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) ***XCHG** is a tempting instruction that's often used in assembly language; for example, exchanging with video memory is a popular way to read and write VGA memory in a single instruction—but it's now a bad idea. As it happens, on the 486 and Pentium, using **MOV**s to read and write memory is faster, anyway; and even on the 486, my measurements indicate a five-cycle tax for **LOCK** in general, and a nine-cycle execution time for **XCHG** with memory. Avoid **XCHG** with memory if you possibly can.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> **XCHG** is a tempting instruction that's often used in assembly +> language; for example, exchanging with video memory is a popular way to +> read and write VGA memory in a single instruction—but it's now a bad +> idea. As it happens, on the 486 and Pentium, using **MOV**s to read and +> write memory is faster, anyway; and even on the 486, my measurements +> indicate a five-cycle tax for **LOCK** in general, and a nine-cycle +> execution time for **XCHG** with memory. Avoid **XCHG** with memory if +> you possibly can. As with the 486, don't use **ENTER** or **LEAVE**, which are slower than the equivalent discrete instructions. Also, start using **TEST diff --git a/19-04.md b/19-04.md index 425e8a1..01f974d 100644 --- a/19-04.md +++ b/19-04.md @@ -22,9 +22,13 @@ the branch or fall-through takes 4 or 5 cycles (if it executes in the U- or V-pipe, respectively)—1 or 2 cycles more than a branch and 3 or 4 cycles more than a fall-through on the 486. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Branch prediction is unprecedented in the x86, and fundamentally alters the nature of pedal-to-the-metal optimization, for the simple reason that it renders unrolled loops largely obsolete. Rare indeed is the loop that can't afford to spare even 1 or 0 (yes, zero!) cycles per iteration for loop counting, and that's how low the cost can go for maintaining a loop on the Pentium.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Branch prediction is unprecedented in the x86, and fundamentally alters +> the nature of pedal-to-the-metal optimization, for the simple reason +> that it renders unrolled loops largely obsolete. Rare indeed is the loop +> that can't afford to spare even 1 or 0 (yes, zero!) cycles per iteration +> for loop counting, and that's how low the cost can go for maintaining a +> loop on the Pentium. Also, unrolled loops are bigger than normal loops, so there are extra (and expensive) cache misses the first time through the loop if the @@ -58,9 +62,13 @@ count on a branch to take 1 cycle when it falls through, but on the Pentium you can't be sure whether it will take 1 or either 4 or 5 cycles on any given iteration. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *As things currently stand, branch prediction is an annoyance for assembly language optimization because it's impossible to be certain exactly how code will perform until you measure it, and even then it's difficult to be sure exactly where the cycles went. All I can say is try to fall through branches if possible, and try to be consistent in your branching if not.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> As things currently stand, branch prediction is an annoyance for +> assembly language optimization because it's impossible to be certain +> exactly how code will perform until you measure it, and even then it's +> difficult to be sure exactly where the cycles went. All I can say is try +> to fall through branches if possible, and try to be consistent in your +> branching if not. ### Miscellaneous Pentium Topics {#Heading9} diff --git a/20-01.md b/20-01.md index 9e30a66..c635eec 100644 --- a/20-01.md +++ b/20-01.md @@ -120,9 +120,13 @@ because those instructions that the V-pipe can handle are able to pair only with certain U-pipe instructions. For example, **MOVSD** uses both pipes, so no instruction can be executed in parallel with **MOVSD**. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *The use of both pipes does make **MOVSD** nearly twice as fast on the Pentium as on the 486, but it's nonetheless slower than using equivalent simpler instructions that allow for superscalar execution. Stick to the Pentium's RISC-like instructions—the pairable instructions I'll discuss next—when you're seeking maximum performance, with just a few exceptions such as **REP MOVS** and **REP STOS**.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> The use of both pipes does make **MOVSD** nearly twice as fast on the +> Pentium as on the 486, but it's nonetheless slower than using equivalent +> simpler instructions that allow for superscalar execution. Stick to the +> Pentium's RISC-like instructions—the pairable instructions I'll discuss +> next—when you're seeking maximum performance, with just a few exceptions +> such as **REP MOVS** and **REP STOS**. Trickier yet, register contention can shut down the V-pipe on any given cycle, and Address Generation Interlocks (AGIs) can stall either pipe at diff --git a/20-02.md b/20-02.md index d66f329..f45758b 100644 --- a/20-02.md +++ b/20-02.md @@ -140,9 +140,14 @@ in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes V-pipe-executable instructions to execute simultaneously (pair) in the V-pipe.** - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *A fundamental rule of Pentium optimization is that it pays to break complex instructions into equivalent simple instructions, then shuffle the simple instructions for maximum use of the V-pipe. This is true partly because most of the pairable instructions are simple instructions, and partly because breaking instructions into pieces allows more freedom to rearrange code to avoid the AGIs and register contention I'll discuss in the next chapter.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> A fundamental rule of Pentium optimization is that it pays to break +> complex instructions into equivalent simple instructions, then shuffle +> the simple instructions for maximum use of the V-pipe. This is true +> partly because most of the pairable instructions are simple +> instructions, and partly because breaking instructions into pieces +> allows more freedom to rearrange code to avoid the AGIs and register +> contention I'll discuss in the next chapter. ![**Figure 20.2**  *Instruction flow through the two pipes.*](images/20-02.jpg) @@ -175,6 +180,8 @@ proper sequencing, interleaving the simple instructions with other instructions that don't use EDX or **Mem Var**, the three-instruction sequence can be reduced to 1.5 cycles, but it is *14* bytes long. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *It's not unusual for Pentium optimization to approximately double both performance and code size at the same time. In an important loop, go for performance and ignore the size, but on a program-wide basis, the size bears watching.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> It's not unusual for Pentium optimization to approximately double both +> performance and code size at the same time. In an important loop, go for +> performance and ignore the size, but on a program-wide basis, the size +> bears watching. diff --git a/20-03.md b/20-03.md index 8baf0dd..d86b9f8 100644 --- a/20-03.md +++ b/20-03.md @@ -39,9 +39,11 @@ fully overlap, as described below). The logical conclusion would seem to be that we should strive to pair instructions of the same lengths, but that is often not correct. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *The actual rule is that we should strive to pair one-cycle instructions (or, at most, two-cycle instructions, but not three-cycle instructions), which in turn leads to the corollary that we should, in general, use mostly one-cycle instructions when optimizing.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> The actual rule is that we should strive to pair one-cycle instructions +> (or, at most, two-cycle instructions, but not three-cycle instructions), +> which in turn leads to the corollary that we should, in general, use +> mostly one-cycle instructions when optimizing. ![**Figure 20.4**  *Lockstep execution and idle time in the V-pipe.*](images/20-04.jpg) diff --git a/20-04.md b/20-04.md index aaa09eb..706dc29 100644 --- a/20-04.md +++ b/20-04.md @@ -78,9 +78,13 @@ complex memory operation; in pre-superscalar days, we used to employ those handy CISC memory instructions to do all that stuff without using any extra registers. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *More problematic still is that for maximum pairing, you'll typically have two operations proceeding at once, one in each pipe, and trying to keep two operations in registers at once is difficult indeed. There's not much to be done about this, other than clever and Spartan register usage, but be aware that it's a major element of Pentium performance programming.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> More problematic still is that for maximum pairing, you'll typically +> have two operations proceeding at once, one in each pipe, and trying to +> keep two operations in registers at once is difficult indeed. There's +> not much to be done about this, other than clever and Spartan register +> usage, but be aware that it's a major element of Pentium performance +> programming. Also be aware that prefixes of every sort, with the sole exception of the 0FH prefix on non-short conditional jumps, always execute in the diff --git a/23-02.md b/23-02.md index 7e8beb8..9965a9e 100644 --- a/23-02.md +++ b/23-02.md @@ -98,29 +98,33 @@ fixed. Moreover, a great deal of graphics software now uses word **OUT**s, so any computer or VGA that doesn't properly support word **OUT**s could scarcely be considered a clone at all. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *A speed tip: The setting of each chip's Index register remains the same until it is reprogrammed. This means that in cases where you are setting the same internal register repeatedly, you can set the Index register to point to that internal register once, then write to the Data register multiple times. For example, the Bit Mask register (GC register 8) is often set repeatedly inside a loop when drawing lines. The standard code for this is:* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - MOV DX,03CEH ;point to GC Index register - MOV AL,8 ;internal index of Bit Mask register - OUT DX,AX ;AH contains Bit Mask register setting - - -- ------------------------------------------------------------------------------------------------------ - *Alternatively, the GC Index register could initially be set to point to the Bit Mask register with* - -- ------------------------------------------------------------------------------------------------------ - - MOV DX,03CEH ;point to GC Index register - MOV AL,8 ;internal index of Bit Mask register - OUT DX,AL ;set GC Index register - INC DX ;point to GC Data register - - -- ------------------------------------------------------------------------------------------------- - *and then the Bit Mask register could be set repeatedly with the byte-size **OUT** instruction* - -- ------------------------------------------------------------------------------------------------- - - OUT DX,AL ;AL contains Bit Mask register setting - - -- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - *which is generally faster (and never slower) than a word-sized **OUT**, and which does not require AH to be set, freeing up a register. Of course, this method only works if the GC Index register remains unchanged throughout the loop.* - -- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> A speed tip: The setting of each chip's Index register remains the same +> until it is reprogrammed. This means that in cases where you are setting +> the same internal register repeatedly, you can set the Index register to +> point to that internal register once, then write to the Data register +> multiple times. For example, the Bit Mask register (GC register 8) is +> often set repeatedly inside a loop when drawing lines. The standard code +> for this is: +> +> MOV DX,03CEH ;point to GC Index register +> MOV AL,8 ;internal index of Bit Mask register +> OUT DX,AX ;AH contains Bit Mask register setting +> +> Alternatively, the GC Index register could initially be set to point to +> the Bit Mask register with +> +> MOV DX,03CEH ;point to GC Index register +> MOV AL,8 ;internal index of Bit Mask register +> OUT DX,AL ;set GC Index register +> INC DX ;point to GC Data register> +> +> and then the Bit Mask register could be set repeatedly with the +> byte-size **OUT** instruction +> +> OUT DX,AL ;AL contains Bit Mask register setting +> +> which is generally faster (and never slower) than a word-sized **OUT**, +> and which does not require AH to be set, freeing up a register. Of +> course, this method only works if the GC Index register remains +> unchanged throughout the loop. \ No newline at end of file diff --git a/23-04.md b/23-04.md index 4cf6fb2..2527dde 100644 --- a/23-04.md +++ b/23-04.md @@ -107,6 +107,10 @@ just to be safe.) Immediately after setting palette RAM, however, 20h Index register to restore normal video, and at all other times bit 5 should be set to 1. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *By the way, palette RAM can be set via the BIOS video interrupt (interrupt 10H), function 10H. Whenever an VGA function can be performed reasonably well through a BIOS function, as it can in the case of setting palette RAM, it should be, both because there is no point in reinventing the wheel and because the BIOS may well mask incompatibilities between the IBM VGA and VGA clones.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> By the way, palette RAM can be set via the BIOS video interrupt +> (interrupt 10H), function 10H. Whenever an VGA function can be performed +> reasonably well through a BIOS function, as it can in the case of +> setting palette RAM, it should be, both because there is no point in +> reinventing the wheel and because the BIOS may well mask +> incompatibilities between the IBM VGA and VGA clones. \ No newline at end of file diff --git a/23-06.md b/23-06.md index 2b05bf0..983d661 100644 --- a/23-06.md +++ b/23-06.md @@ -24,9 +24,24 @@ sync to happen, sets the new pel panning state, and then continues drawing. Don't worry about the details right now; page flipping will come up again, at considerably greater length, in later chapters. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *As an interesting side note, be aware that if you run DOS software under a multitasking environment such as Windows NT, timeslicing delays can make mismatched start address bytes or mismatched start address and pel panning settings much more likely, for the graphics code can be interrupted at any time. This is also possible, although much less likely, under non-multitasking environments such as DOS, because strategically placed interrupts can cause the same sorts of problems there. For maximum safety, you should disable interrupts around the key portions of your page-flipping code, although here we run into the problem that if interrupts are disabled from the time we start looking for Display Enable until we set the Pel Panning register, they will be off for far too long, and keyboard, mouse, and network events will potentially be lost. Also, disabling interrupts won't help in true multitasking environments, which never let a program hog the entire CPU. This is one reason that pel panning, although indubitably flashy, isn't widely used and should be reserved for only those cases where it's absolutely necessary.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> As an interesting side note, be aware that if you run DOS software under +> a multitasking environment such as Windows NT, timeslicing delays can +> make mismatched start address bytes or mismatched start address and pel +> panning settings much more likely, for the graphics code can be +> interrupted at any time. This is also possible, although much less +> likely, under non-multitasking environments such as DOS, because +> strategically placed interrupts can cause the same sorts of problems +> there. For maximum safety, you should disable interrupts around the key +> portions of your page-flipping code, although here we run into the +> problem that if interrupts are disabled from the time we start looking +> for Display Enable until we set the Pel Panning register, they will be +> off for far too long, and keyboard, mouse, and network events will +> potentially be lost. Also, disabling interrupts won't help in true +> multitasking environments, which never let a program hog the entire CPU. +> This is one reason that pel panning, although indubitably flashy, isn't +> widely used and should be reserved for only those cases where it's +> absolutely necessary. Waiting for the sync pulse has the side effect of causing program execution to synchronize to the VGA's frame rate of 60 or 70 frames per diff --git a/24-03.md b/24-03.md index 10758c4..9a7dcbb 100644 --- a/24-03.md +++ b/24-03.md @@ -110,9 +110,18 @@ separate byte values, making matters easier for the programmer, while the macro itself can combine the values into a single word-sized constant. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *A minor optimization tip illustrated in the listing is the use of **INC AX** and **DEC AX** in the **DrawVerticalBox** subroutine when only AL actually needs to be modified. Word-sized register increment and decrement instructions (or dword-sized instructions in 32-bit protected mode) are only one byte long, while byte-size register increment and decrement instructions are two bytes long. Consequently, when size counts, it is worth using a whole 16-bit (or 32-bit) register instead of the low 8 bits of that register for **INC** and **DEC**—if you don't need the upper portion of the register for any other purpose, or if you can be sure that the **INC** or **DEC** won't affect the upper part of the register.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> A minor optimization tip illustrated in the listing is the use of **INC +> AX** and **DEC AX** in the **DrawVerticalBox** subroutine when only AL +> actually needs to be modified. Word-sized register increment and +> decrement instructions (or dword-sized instructions in 32-bit protected +> mode) are only one byte long, while byte-size register increment and +> decrement instructions are two bytes long. Consequently, when size +> counts, it is worth using a whole 16-bit (or 32-bit) register instead of +> the low 8 bits of that register for **INC** and **DEC**—if you don't +> need the upper portion of the register for any other purpose, or if you +> can be sure that the **INC** or **DEC** won't affect the upper part of +> the register. The latches and ALUs are central to high-performance VGA code, since they allow programs to process across all four memory planes without a diff --git a/25-03.md b/25-03.md index f96d31f..0098e58 100644 --- a/25-03.md +++ b/25-03.md @@ -24,9 +24,16 @@ mask; unfortunately, we're already using it for stencilling in this case), but it completely eliminates reading display memory and does foreground-plus-background drawing in one blurry-fast pass. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This last-described example is a good illustration of how I'd suggest you approach the VGA: As a rich collection of hardware resources that can profitably be combined in some non-obvious ways. Don't let yourself be limited by the obvious applications for the latches, bit mask, write modes, read modes, map mask, ALUs, and set/reset circuitry. Instead, try to imagine how they could work together to perform whatever task you happen to need done at any given time. I've made my code as much as four times faster by doing this, as the discussion of Mode X in Chapters 47-49 demonstrates.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> This last-described example is a good illustration of how I'd suggest +> you approach the VGA: As a rich collection of hardware resources that +> can profitably be combined in some non-obvious ways. Don't let yourself +> be limited by the obvious applications for the latches, bit mask, write +> modes, read modes, map mask, ALUs, and set/reset circuitry. Instead, try +> to imagine how they could work together to perform whatever task you +> happen to need done at any given time. I've made my code as much as four +> times faster by doing this, as the discussion of Mode X in Chapters +> 47-49 demonstrates. The example code in Listing 25.1 is designed to illustrate the use of the Data Rotate and Bit Mask registers, and is not as fast or as diff --git a/25-06.md b/25-06.md index 803498e..d864e4c 100644 --- a/25-06.md +++ b/25-06.md @@ -31,9 +31,17 @@ Set/Reset register is inactive in write mode 3, but the Set/Reset register provides the primary drawing color in write mode 3, as discussed in the next chapter. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Be aware that because set/reset directly replaces CPU data, it does not necessarily have to force an entire display memory byte to 0 or 0FFH, even when set/reset is replacing CPU data for all planes. For example, if the Bit Mask register is set to 80H, the set/reset circuitry can only modify bit 7 of the destination byte in each plane, since the other seven bits will come from the latches for each plane. Similarly, the set/reset value for each plane can be modified by that plane's ALU. Once again, this illustrates that set/reset merely replaces the CPU data for selected planes; the set/reset value is then processed in exactly the same way that CPU data normally is.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Be aware that because set/reset directly replaces CPU data, it does not +> necessarily have to force an entire display memory byte to 0 or 0FFH, +> even when set/reset is replacing CPU data for all planes. For example, +> if the Bit Mask register is set to 80H, the set/reset circuitry can only +> modify bit 7 of the destination byte in each plane, since the other +> seven bits will come from the latches for each plane. Similarly, the +> set/reset value for each plane can be modified by that plane's ALU. Once +> again, this illustrates that set/reset merely replaces the CPU data for +> selected planes; the set/reset value is then processed in exactly the +> same way that CPU data normally is. ### A Brief Note on Word OUTs {#Heading9} diff --git a/26-03.md b/26-03.md index 186f445..b8c1585 100644 --- a/26-03.md +++ b/26-03.md @@ -17,9 +17,16 @@ equal to 1 indicating where character pixels are to appear. The Data Rotate register is set to rotate the CPU data to pixel-align it, since without rotation characters could only be drawn on byte boundaries. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *As I pointed out in Chapter 25, the CPU is perfectly capable of rotating the data itself, and it's often the case that that's more efficient. The problem with using the Data Rotate register is that the **OUT** that sets that register is time-consuming, especially for proportional text, which requires a different rotation for each character. Also, if the code performs full-byte accesses to display memory—that is, if it combines pieces of two adjacent characters into one byte—whenever possible for efficiency, the CPU generally has to do extra work to prepare the data so the VGA's rotator can handle it.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> As I pointed out in Chapter 25, the CPU is perfectly capable of rotating +> the data itself, and it's often the case that that's more efficient. The +> problem with using the Data Rotate register is that the **OUT** that +> sets that register is time-consuming, especially for proportional text, +> which requires a different rotation for each character. Also, if the +> code performs full-byte accesses to display memory—that is, if it +> combines pieces of two adjacent characters into one byte—whenever +> possible for efficiency, the CPU generally has to do extra work to +> prepare the data so the VGA's rotator can handle it. At the same time that the Data Rotate register is set, the Bit Mask register is set to allow the CPU to modify only that portion of the diff --git a/27-01.md b/27-01.md index f148851..e85b24e 100644 --- a/27-01.md +++ b/27-01.md @@ -94,9 +94,11 @@ register is set to 1. ![**Figure 27.1**  *VGA data flow in write mode 2.*](images/27-01.jpg) - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *It's worth noting two differences between write mode 2 and write mode 0, the standard write mode of the VGA. First, rotation of the CPU data byte does not take place in write mode 2. Second, the Set/Reset and Enable Set/Reset registers have no effect in write mode 2.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> It's worth noting two differences between write mode 2 and write mode 0, +> the standard write mode of the VGA. First, rotation of the CPU data byte +> does not take place in write mode 2. Second, the Set/Reset and Enable +> Set/Reset registers have no effect in write mode 2. Now that we understand the mechanics of write mode 2, we can step back and get a feel for what it might be useful for. View bits 3-0 of the CPU diff --git a/27-03.md b/27-03.md index 58906b6..c119d86 100644 --- a/27-03.md +++ b/27-03.md @@ -15,9 +15,14 @@ pages: 508-515 into VGA bitmaps does have its uses, Listing 27.1 is primarily intended to illustrate the mechanics of write mode 2. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *For performance, it's best to store 16-color bitmaps in pre-separated four-plane format in system memory, and copy one plane at a time to the screen. Ideally, such bitmaps should be copied one scan line at a time, with all four planes completed for one scan line before moving on to the next. I say this because when entire images are copied one plane at a time, nasty transient color effects can occur as one plane becomes visibly changed before other planes have been modified.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> For performance, it's best to store 16-color bitmaps in pre-separated +> four-plane format in system memory, and copy one plane at a time to the +> screen. Ideally, such bitmaps should be copied one scan line at a time, +> with all four planes completed for one scan line before moving on to the +> next. I say this because when entire images are copied one plane at a +> time, nasty transient color effects can occur as one plane becomes +> visibly changed before other planes have been modified. #### Drawing Color-Patterned Lines Using Write Mode 2 {#Heading6} diff --git a/28-03.md b/28-03.md index e6343d5..03e0f0d 100644 --- a/28-03.md +++ b/28-03.md @@ -18,9 +18,18 @@ far better VGA animation techniques. As you'd expect, neither the read mode nor the setting of the Read Map register affects CPU *writes* to VGA memory in any way. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *An important point regarding reading VGA memory involves the VGA's latches. (Remember that each of the four latches stores a byte for one plane; on CPU writes, the latches can provide some or all of the data written to display memory, allowing fast copying and efficient pixel masking.) Whenever the CPU reads a given address in VGA memory, each of the four latches is loaded with the contents of the byte at that address in its respective plane. Even though the CPU only receives data from one plane in read mode 0, all four planes are always read, and the values read are stored in the latches. This is true in read mode 1 as well. In short, whenever the CPU reads VGA memory in any read mode, all four planes are read and all four latches are always loaded.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> An important point regarding reading VGA memory involves the VGA's +> latches. (Remember that each of the four latches stores a byte for one +> plane; on CPU writes, the latches can provide some or all of the data +> written to display memory, allowing fast copying and efficient pixel +> masking.) Whenever the CPU reads a given address in VGA memory, each of +> the four latches is loaded with the contents of the byte at that address +> in its respective plane. Even though the CPU only receives data from one +> plane in read mode 0, all four planes are always read, and the values +> read are stored in the latches. This is true in read mode 1 as well. In +> short, whenever the CPU reads VGA memory in any read mode, all four +> planes are read and all four latches are always loaded. ### Read Mode 1 {#Heading4} diff --git a/29-03.md b/29-03.md index 323d62b..edf423e 100644 --- a/29-03.md +++ b/29-03.md @@ -10,9 +10,16 @@ chapter: '29' pages: 547-550 --- - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *While these requirements are no problem if you're simply calling a subroutine in order to save an image from your program, they pose a considerable problem if you're designing a hot-key operated TSR that can capture a screen image at any time. With the EGA specifically, there's never any way to tell what state the registers are currently in, since the registers aren't readable. (More on this issue later in this chapter.) As a result, any TSR that sets the Bit Mask to 0FFH, the Data Rotate register to 0, and so on runs the risk of interfering with the drawing code of the program that's already running.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> While these requirements are no problem if you're simply calling a +> subroutine in order to save an image from your program, they pose a +> considerable problem if you're designing a hot-key operated TSR that can +> capture a screen image at any time. With the EGA specifically, there's +> never any way to tell what state the registers are currently in, since +> the registers aren't readable. (More on this issue later in this +> chapter.) As a result, any TSR that sets the Bit Mask to 0FFH, the Data +> Rotate register to 0, and so on runs the risk of interfering with the +> drawing code of the program that's already running. What's the solution? Frankly, the solution is to get VGA-specific. A TSR designed for the VGA can simply read out and save the state of the @@ -52,9 +59,15 @@ as long as the high-level language you're using can perform direct port I/O to set up the adapter and can read and write display memory directly. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *One tip if you're saving and restoring the screen from a high-level language on an EGA, though: After you've completed the save or restore operation, be sure to put any registers that you've changed back to their default settings. Some high-level languages (and the BIOS as well) assume that various registers are left in a certain state, so on the EGA it's safest to leave the registers in their most likely state. On the VGA, of course, you can just read the registers out before you change them, then put them back the way you found them when you're done.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> One tip if you're saving and restoring the screen from a high-level +> language on an EGA, though: After you've completed the save or restore +> operation, be sure to put any registers that you've changed back to +> their default settings. Some high-level languages (and the BIOS as well) +> assume that various registers are left in a certain state, so on the EGA +> it's safest to leave the registers in their most likely state. On the +> VGA, of course, you can just read the registers out before you change +> them, then put them back the way you found them when you're done. ### 16 Colors out of 64 {#Heading4} diff --git a/29-05.md b/29-05.md index bdefbac..fcbb94a 100644 --- a/29-05.md +++ b/29-05.md @@ -20,9 +20,14 @@ programmed to any of the 64 possible colors by either setting Attribute Controller register 11H directly or calling video function 10H, subfunction 1. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *On ECD-compatible monitors, however, there's too little scan time to display a proper border when the EGA is in 350-scan-line mode, so overscan should always be 0 (black) unless you're in 200-scanmode. Note, though, that a VGA can easily display a border on a VGA-compatible monitor, and VGAs are in fact programmed at mode set for an 8-pixel-wide border in all modes; all you need do is set the overscan color on any VGA to see the border.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> On ECD-compatible monitors, however, there's too little scan time to +> display a proper border when the EGA is in 350-scan-line mode, so +> overscan should always be 0 (black) unless you're in 200-scanmode. Note, +> though, that a VGA can easily display a border on a VGA-compatible +> monitor, and VGAs are in fact programmed at mode set for an 8-pixel-wide +> border in all modes; all you need do is set the overscan color on any +> VGA to see the border. ### A Bonus Blanker {#Heading6} diff --git a/30-03.md b/30-03.md index 660323d..847844e 100644 --- a/30-03.md +++ b/30-03.md @@ -57,9 +57,12 @@ screen start scan line is set—that seems to me to be an advantage—but because the changed screen can appear *before* the new split screen start scan line is set. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Remember, the split screen start scan line is spread out over two or three registers. What if the incompletely-changed value matches the current scan line after you've set one register but before you've set the rest? For one frame, you'll see the split screen in a wrong place—possibly a very wrong place—resulting in jumping and flicker.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Remember, the split screen start scan line is spread out over two or +> three registers. What if the incompletely-changed value matches the +> current scan line after you've set one register but before you've set +> the rest? For one frame, you'll see the split screen in a wrong +> place—possibly a very wrong place—resulting in jumping and flicker. The solution is simple: Set the split screen start scan line at a time when it can't possibly match the currently displayed scan line. The easy @@ -103,9 +106,12 @@ designing split-screen images that might be displayed on EGAs, and you should in any case check how your split-screens look on both VGAs and EGAs. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *I have an important caution here: Don't count on the EGA's split-screen bug; that is, don't rely on the first scan line being doubled when you design your split screens. IBM designed and made the original EGA, but a lot of companies cloned it, and there's no guarantee that all EGA clones copy the bug. It is a certainty, at least, that the VGA didn't copy it.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> I have an important caution here: Don't count on the EGA's split-screen +> bug; that is, don't rely on the first scan line being doubled when you +> design your split screens. IBM designed and made the original EGA, but a +> lot of companies cloned it, and there's no guarantee that all EGA clones +> copy the bug. It is a certainty, at least, that the VGA didn't copy it. There's another respect in which the EGA is inferior to the VGA when it comes to the split screen, and that's in the area of panning when the diff --git a/30-04.md b/30-04.md index 7d871a6..bea9e4f 100644 --- a/30-04.md +++ b/30-04.md @@ -36,9 +36,11 @@ current byte. As the top part of the screen moves smoothly about, the split screen will move and jump, move and jump, over and over. Believe me, it's not a pretty sight. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *What's to be done? On the EGA, nothing. Unless you're willing to have your users' eyes doing the jitterbug, don't use horizontal smooth scrolling while the split screen is up. Byte panning is fine—just don't change the Pel Panning register from its default setting.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> What's to be done? On the EGA, nothing. Unless you're willing to have +> your users' eyes doing the jitterbug, don't use horizontal smooth +> scrolling while the split screen is up. Byte panning is fine—just don't +> change the Pel Panning register from its default setting. On the VGA, there is recourse. A VGA-only bit, bit 5 of the AC Mode Control register (AC register 10H), turns off pel panning in the split diff --git a/30-06.md b/30-06.md index 600b7f1..5ac870b 100644 --- a/30-06.md +++ b/30-06.md @@ -62,9 +62,14 @@ I'm sure Richard is right when it comes to the real McCoy IBM VGA and EGA, but I'm less confident that every clone out there loads the start address at the start of vertical sync. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *For that very reason, I generally advise people not to use horizontal smooth panning unless they can test their software on all the makes of display adapter it might run on. I've used Richard's approach in Listings 30.1 and 30.2, and so far as I've seen it works fine, but be aware that there are potential, albeit unproven, hazards to relying on the setting of the start address registers to occur at a specific time in the frame.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> For that very reason, I generally advise people not to use horizontal +> smooth panning unless they can test their software on all the makes of +> display adapter it might run on. I've used Richard's approach in +> Listings 30.1 and 30.2, and so far as I've seen it works fine, but be +> aware that there are potential, albeit unproven, hazards to relying on +> the setting of the start address registers to occur at a specific time +> in the frame. The interaction of the start address registers and the Pel Panning register is worthy of note. After waiting for the end of vertical sync diff --git a/32-03.md b/32-03.md index 22f8ca7..53baff3 100644 --- a/32-03.md +++ b/32-03.md @@ -217,6 +217,11 @@ The first thing you'll notice when you run this code is that the speed of 360x480 256-color mode is pretty good, especially considering that most of the program is im-plemented in C. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Drawing in 360x480 256-color mode can sometimes actually be faster than in the 16-color modes, because the byte-per-pixel display memory organization of 256-color mode eliminates the need to read display memory before writing to it in order to isolate individual pixels coexisting within a single byte. In addition, 360x480 256-color mode is a variant of Mode X, which we'll encounter in detail in Chapter 47, and supports all the high-performance features of Mode X.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Drawing in 360x480 256-color mode can sometimes actually be faster than +> in the 16-color modes, because the byte-per-pixel display memory +> organization of 256-color mode eliminates the need to read display +> memory before writing to it in order to isolate individual pixels +> coexisting within a single byte. In addition, 360x480 256-color mode is +> a variant of Mode X, which we'll encounter in detail in Chapter 47, and +> supports all the high-performance features of Mode X. \ No newline at end of file diff --git a/34-02.md b/34-02.md index 412fa8c..e6fc5c0 100644 --- a/34-02.md +++ b/34-02.md @@ -50,9 +50,17 @@ load all 256 DAC locations without showing *some* sort of garbage on the screen for at least one frame, but that's not the BIOS's fault; it's a problem endemic to the VGA. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *These findings lead me inexorably to the conclusion that the BIOS should not be used to load the DAC dynamically. That is, if you're loading the DAC just once in preparation for a graphics session—sort of a DAC mode set—by all means load by way of the BIOS. No one will care that some garbage is displayed for a single frame; heck, I have boards that bounce and flicker and show garbage every time I do a mode set, and the amount of garbage produced by loading the DAC once is far less noticeable. If, however, you intend to load the DAC repeatedly for color cycling, avoid the BIOS DAC load functions like the plague. They will bring you only heartache.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> These findings lead me inexorably to the conclusion that the BIOS should +> not be used to load the DAC dynamically. That is, if you're loading the +> DAC just once in preparation for a graphics session—sort of a DAC mode +> set—by all means load by way of the BIOS. No one will care that some +> garbage is displayed for a single frame; heck, I have boards that bounce +> and flicker and show garbage every time I do a mode set, and the amount +> of garbage produced by loading the DAC once is far less noticeable. If, +> however, you intend to load the DAC repeatedly for color cycling, avoid +> the BIOS DAC load functions like the plague. They will bring you only +> heartache. As but one example of the unsuitability of the BIOS DAC-loading functions for color cycling, imagine that you want to cycle all 256 diff --git a/34-04.md b/34-04.md index d004848..c4fdab3 100644 --- a/34-04.md +++ b/34-04.md @@ -125,6 +125,15 @@ much the same for the jets. The only remaining task would be to animate the spaceship across the screen, which is not a particularly difficult task. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The key to getting all the color cycling to work in the above example, however, would be to assign each color cycling task a different part of the DAC, with each part cycled independently as needed. If, as is likely, the total number of DAC locations cycled proved to be too great to manage in one frame, you could simply cycle the colors of the stars after one frame, the colors of the meteors after the next, and the colors of the jets after yet another frame, then back around to cycling the colors of the stars. By splitting up the DAC in this manner and interleaving the cycling tasks, you can perform a great deal of seemingly complex color animation without loading very much of the DAC during any one frame.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The key to getting all the color cycling to work in the above example, +> however, would be to assign each color cycling task a different part of +> the DAC, with each part cycled independently as needed. If, as is +> likely, the total number of DAC locations cycled proved to be too great +> to manage in one frame, you could simply cycle the colors of the stars +> after one frame, the colors of the meteors after the next, and the +> colors of the jets after yet another frame, then back around to cycling +> the colors of the stars. By splitting up the DAC in this manner and +> interleaving the cycling tasks, you can perform a great deal of +> seemingly complex color animation without loading very much of the DAC +> during any one frame. \ No newline at end of file diff --git a/35-04.md b/35-04.md index 875762b..78683cc 100644 --- a/35-04.md +++ b/35-04.md @@ -123,9 +123,17 @@ orientations are distinguished by which coordinate forms the major axis and by whether each of X and Y increases or decreases from the line start to the line end. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *A moment of thought will show, however, that four of the line orientations are redundant. Each of the four orientations for which **DeltaY**, the Y component of the line, is less than 0 (that is, for which the line start Y coordinate is greater than the line end Y coordinate) can be transformed into one of the four orientations for which the line start Y coordinate is less than the line end Y coordinate simply by reversing the line start and end coordinates, so that the line is drawn in the other direction. **EVGALine** does this by swapping (X0,Y0) (the line start coordinates) with (X1,Y1) (the line end coordinates) whenever Y0 is greater than Y1.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> A moment of thought will show, however, that four of the line +> orientations are redundant. Each of the four orientations for which +> **DeltaY**, the Y component of the line, is less than 0 (that is, for +> which the line start Y coordinate is greater than the line end Y +> coordinate) can be transformed into one of the four orientations for +> which the line start Y coordinate is less than the line end Y coordinate +> simply by reversing the line start and end coordinates, so that the line +> is drawn in the other direction. **EVGALine** does this by swapping +> (X0,Y0) (the line start coordinates) with (X1,Y1) (the line end +> coordinates) whenever Y0 is greater than Y1. This accomplished, **EVGALine** must still distinguish among the four remaining line orientations. Those four orientations form two major diff --git a/37-02.md b/37-02.md index bb850b3..386061b 100644 --- a/37-02.md +++ b/37-02.md @@ -55,9 +55,13 @@ 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?* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](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 diff --git a/38-02.md b/38-02.md index 43e0853..0a6f087 100644 --- a/38-02.md +++ b/38-02.md @@ -44,9 +44,14 @@ range around a pixel, then that pixel will be drawn once and only once—just what we need in order to be able to fit filled polygons together seamlessly. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This sort of non-overlapping polygon filling isn't ideal for all purposes. Polygons are skewed toward the top and left edges, which not only introduces drawing error relative to the ideal polygon but also means that a filled polygon won't match the same polygon drawn unfilled. Narrow wedges and one-pixel-wide polygons will show up spottily. All in all, the choice of polygon-filling approach depends entirely on the ways in which the filled polygons must be used.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> This sort of non-overlapping polygon filling isn't ideal for all +> purposes. Polygons are skewed toward the top and left edges, which not +> only introduces drawing error relative to the ideal polygon but also +> means that a filled polygon won't match the same polygon drawn unfilled. +> Narrow wedges and one-pixel-wide polygons will show up spottily. All in +> all, the choice of polygon-filling approach depends entirely on the ways +> in which the filled polygons must be used. For our purposes, nonoverlapping polygons are the way to go, so let's have at them. diff --git a/39-02.md b/39-02.md index d508b71..e076457 100644 --- a/39-02.md +++ b/39-02.md @@ -125,6 +125,6 @@ the floating point results were sufficiently imprecise to creep from just under an integer value to just over it, so that the **ceil** function returned a coordinate that was one too large. - ------------------- --------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Floating point is very accurate—but it is not precise. Integer calculations, properly performed, are.* - ------------------- --------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Floating point is very accurate—but it is not precise. Integer +> calculations, properly performed, are. \ No newline at end of file diff --git a/39-04.md b/39-04.md index 2886cce..a3867e8 100644 --- a/39-04.md +++ b/39-04.md @@ -17,10 +17,19 @@ states. With those wait states factored out, the assembly language version of **DrawHorizontalLineList** becomes almost three times as fast as the C code. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *There is a lesson here. An optimization has no fixed payoff; its value fluctuates according to the context in which it is used. There's relatively little benefit to further optimizing code that already spends half its time waiting for display memory; no matter how good your optimizations, you'll get only a two-times speedup at best, and generally much less than that. There is, on the other hand, potential for tremendous improvement when drawing to system memory, so if that's where most of your drawing will occur, optimizations such as Listing 39.3 are well worth the effort.* - *Know the environments in which your code will run, and know where the cycles go in those environments.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> There is a lesson here. An optimization has no fixed payoff; its value +> fluctuates according to the context in which it is used. There's +> relatively little benefit to further optimizing code that already spends +> half its time waiting for display memory; no matter how good your +> optimizations, you'll get only a two-times speedup at best, and +> generally much less than that. There is, on the other hand, potential +> for tremendous improvement when drawing to system memory, so if that's +> where most of your drawing will occur, optimizations such as Listing +> 39.3 are well worth the effort. +> +> Know the environments in which your code will run, and know where the +> cycles go in those environments. **LISTING 39.3 L39-3.ASM** diff --git a/40-05.md b/40-05.md index 9aaade1..c065ed9 100644 --- a/40-05.md +++ b/40-05.md @@ -23,9 +23,11 @@ back links and insertion sorting aren't great enough; profiling a variety of polygons reveals that less than ten percent of total time is spent sorting the AET. - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The potential 1 to 5 percent speedup gained by optimizing AET sorting just isn't worth it in any but the most demanding application—a good example of the need to keep an overall perspective when comparing the theoretical characteristics of various approaches.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> The potential 1 to 5 percent speedup gained by optimizing AET sorting +> just isn't worth it in any but the most demanding application—a good +> example of the need to keep an overall perspective when comparing the +> theoretical characteristics of various approaches. ### Nonconvex Polygons {#Heading8} diff --git a/41-01.md b/41-01.md index 72ee216..a1b13a9 100644 --- a/41-01.md +++ b/41-01.md @@ -47,9 +47,15 @@ called "nonconvex" is actually "simple," and I suppose what I called "complex" should be referred to as "nonsimple," or maybe just "none of the above." - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This may seem like nit-picking, but actually, it isn't; what it's really about is the tremendous importance of having a shared language. In one of his books, Richard Feynman describes having developed his own mathematical framework, complete with his own notation and terminology, in high school. When he got to college and started working with other people who were at his level, he suddenly understood that people can't share ideas effectively unless they speak the same language; otherwise, they waste a great deal of time on misunderstandings and explanation.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> This may seem like nit-picking, but actually, it isn't; what it's really +> about is the tremendous importance of having a shared language. In one +> of his books, Richard Feynman describes having developed his own +> mathematical framework, complete with his own notation and terminology, +> in high school. When he got to college and started working with other +> people who were at his level, he suddenly understood that people can't +> share ideas effectively unless they speak the same language; otherwise, +> they waste a great deal of time on misunderstandings and explanation. Or, as Bill Huber put it, "You are free to adopt your own terminology when it suits your purposes well. But you risk losing or confusing those diff --git a/42-05.md b/42-05.md index a9230fa..e8a3789 100644 --- a/42-05.md +++ b/42-05.md @@ -297,9 +297,15 @@ accurate polygon-antialiasing approach around Wu's basic weighting technique. The results would not be quite so good as more sophisticated antialiasing techniques, but they would be much faster. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In general, the results obtained by Wu antialiasing are only so-so, by theoretical measures. Wu antialiasing amounts to a simple box filter placed over a fixed-point step approximation of a line, and that process introduces a good deal of deviation from the ideal. On the other hand, Wu notes that even a 10 percent error in intensity doesn't lead to noticeable loss of image quality, and for Wu-antialiased lines up to 1K pixels in length, the error is under 10 percent. If it looks good, it is good—and it looks good.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> In general, the results obtained by Wu antialiasing are only so-so, by +> theoretical measures. Wu antialiasing amounts to a simple box filter +> placed over a fixed-point step approximation of a line, and that process +> introduces a good deal of deviation from the ideal. On the other hand, +> Wu notes that even a 10 percent error in intensity doesn't lead to +> noticeable loss of image quality, and for Wu-antialiased lines up to 1K +> pixels in length, the error is under 10 percent. If it looks good, it is +> good—and it looks good. With a 16-bit error accumulator, fixed-point inaccuracy becomes a problem for Wu-antialiased lines longer than 1K. For such lines, you diff --git a/44-04.md b/44-04.md index 691cefd..7fa8047 100644 --- a/44-04.md +++ b/44-04.md @@ -107,9 +107,16 @@ set/reset color: mov es,dx mov byte ptr es:[di],0ffh - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *If you're familiar with VGA programming, you're no doubt aware that everything that can be done with write mode 3 can also be accomplished in write mode 0 or write mode 2 by using the Bit Mask register. However, setting the Bit Mask register requires at least one **OUT** per byte written, in addition to the read and write of display memory, and **OUT**s are often slower than display memory accesses, especially on 386s and 486s. One of the great virtues of write mode 3 is that it requires virtually no **OUT**s and is therefore substantially faster for masking than the other write modes.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> If you're familiar with VGA programming, you're no doubt aware that +> everything that can be done with write mode 3 can also be accomplished +> in write mode 0 or write mode 2 by using the Bit Mask register. However, +> setting the Bit Mask register requires at least one **OUT** per byte +> written, in addition to the read and write of display memory, and +> **OUT**s are often slower than display memory accesses, especially on +> 386s and 486s. One of the great virtues of write mode 3 is that it +> requires virtually no **OUT**s and is therefore substantially faster for +> masking than the other write modes. In short, write mode 3 is a good choice for single-color drawing that modifies individual pixels within display memory bytes. Not diff --git a/44-05.md b/44-05.md index bc7f534..d2fcc59 100644 --- a/44-05.md +++ b/44-05.md @@ -20,9 +20,21 @@ using write mode 3 because write mode 3 requires a read/write of display memory (or at least preloading the latches with the background color), while the write mode 0 approach requires only a write to display memory. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Is write mode 0 always the best way to do text? Not at all. The write mode 0 approach described above draws both foreground and background pixels within the character box, forcing the background pixels to black at the same time that it forces the foreground pixels to white. If you want to draw transparent text (that is, draw only the character pixels, not the surrounding background box), write mode 3 is ideal. Also, matters get far more complicated if characters that aren't 8 pixels wide are drawn, or if characters are drawn starting at arbitrary pixel locations, without the multiple-of-8 column restriction, so that rotation and masking are required. Lastly, the Map Mask register can be used to draw text in colors other than white—but only if the background is black. Otherwise, the data remaining in the planes protected by the Map Mask will remain and can interfere with the colors of the text being drawn.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Is write mode 0 always the best way to do text? Not at all. The write +> mode 0 approach described above draws both foreground and background +> pixels within the character box, forcing the background pixels to black +> at the same time that it forces the foreground pixels to white. If you +> want to draw transparent text (that is, draw only the character pixels, +> not the surrounding background box), write mode 3 is ideal. Also, +> matters get far more complicated if characters that aren't 8 pixels wide +> are drawn, or if characters are drawn starting at arbitrary pixel +> locations, without the multiple-of-8 column restriction, so that +> rotation and masking are required. Lastly, the Map Mask register can be +> used to draw text in colors other than white—but only if the background +> is black. Otherwise, the data remaining in the planes protected by the +> Map Mask will remain and can interfere with the colors of the text being +> drawn. I'm not going to delve any deeper into the considerable issues of drawing VGA text; I just want to sensitize you to the existence of diff --git a/45-02.md b/45-02.md index 54dff78..643b221 100644 --- a/45-02.md +++ b/45-02.md @@ -36,11 +36,12 @@ memory tends not to be as bad as VGA I/O, but lord knows it isn't Table: Table 45.1 Results of I/O performance tests run under the Phar Lap386|DOS-Extender. -* * * * * - - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) ***OUT**s, in general, are lousy on the 486 (and to think they only took three cycles on the 286!). **OUT**s to VGAs are particularly lousy. Display memory performance is pretty poor, especially for reads. The conclusions are obvious, I would hope. Structure your graphics code, and, in general, all 486 code, to avoid **OUT**s.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> **OUT**s, in general, are lousy on the 486 (and to think they only took +> three cycles on the 286!). **OUT**s to VGAs are particularly lousy. +> Display memory performance is pretty poor, especially for reads. The +> conclusions are obvious, I would hope. Structure your graphics code, +> and, in general, all 486 code, to avoid **OUT**s. For graphics, this especially means using write mode 3 rather than the bit-mask register. When you must use the bit mask, arrange drawing so diff --git a/45-04.md b/45-04.md index ffea412..7030da2 100644 --- a/45-04.md +++ b/45-04.md @@ -22,9 +22,13 @@ bitmap into the system-memory buffer, then copy the buffer to the screen, and the effect will be of having panned the window to the new location. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Another argument in favor of a small viewing window is that it restricts the amount of display memory actually drawn to. Restricting the display memory used for animation reduces the total number of display-memory accesses, which in turn boosts overall performance; it also improves the performance and appearance of panning, in which the whole window has to be redrawn or copied.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Another argument in favor of a small viewing window is that it restricts +> the amount of display memory actually drawn to. Restricting the display +> memory used for animation reduces the total number of display-memory +> accesses, which in turn boosts overall performance; it also improves the +> performance and appearance of panning, in which the whole window has to +> be redrawn or copied. If you keep a close watch, you'll notice that many high-performance animation games similarly restrict their full-featured animation area to diff --git a/47-05.md b/47-05.md index d4bcfcb..da03479 100644 --- a/47-05.md +++ b/47-05.md @@ -10,9 +10,15 @@ chapter: '47' pages: 887-889 --- - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In general, performing plane-at-a-time operations can make almost any Mode X operation, at the worst, nearly as fast as the same operation in mode 13H (although this sort of Mode X programming is admittedly fairly complex). In this pursuit, it can help to organize data structures with Mode X in mind. For example, icons could be prearranged in system memory with the pixels organized into four plane-oriented sets (or, again, in four sets per scan line to avoid a fading-in effect) to facilitate copying to the screen a plane at a time with **REP MOVS*** - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> In general, performing plane-at-a-time operations can make almost any +> Mode X operation, at the worst, nearly as fast as the same operation in +> mode 13H (although this sort of Mode X programming is admittedly fairly +> complex). In this pursuit, it can help to organize data structures with +> Mode X in mind. For example, icons could be prearranged in system memory +> with the pixels organized into four plane-oriented sets (or, again, in +> four sets per scan line to avoid a fading-in effect) to facilitate +> copying to the screen a plane at a time with **REP MOVS**. **LISTING 47.5 L47-5.ASM** diff --git a/48-03.md b/48-03.md index d78dde7..debe2be 100644 --- a/48-03.md +++ b/48-03.md @@ -83,6 +83,10 @@ set the Read Map register to select a source plane and the Map Mask register to select the corresponding destination plane. Then, copy all pixels in that plane, repeating for all four planes.) - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Although copying through the latches is, in general, a speedy technique, especially on slower VGAs, it's not always a win. Reading video memory tends to be quite a bit slower than writing, and on a fast VLB or PCI adapter, it can be faster to copy from main memory to display memory than it is to copy from display memory to display memory via the latches.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Although copying through the latches is, in general, a speedy technique, +> especially on slower VGAs, it's not always a win. Reading video memory +> tends to be quite a bit slower than writing, and on a fast VLB or PCI +> adapter, it can be faster to copy from main memory to display memory +> than it is to copy from display memory to display memory via the +> latches. \ No newline at end of file diff --git a/49-03.md b/49-03.md index 4a3c9fe..70c6cbc 100644 --- a/49-03.md +++ b/49-03.md @@ -129,6 +129,12 @@ in, and simply pass pointers to these structures to the low level, rather than passing many separate parameters, as is now the case. I've used separate parameters for simplicity and flexibility. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Be aware that as nifty as Mode X hardware-assisted masked copying is, whether or not it's actually faster than software-only masked or transparent copying depends upon the processor and the video adapter. The advantage of Mode X masked copying is the 32-bit parallelism; the disadvantages are the need to read display memory and the need to perform an **OUT** for every four pixels. (**OUT** is a slow 486/Pentium instruction, and most VGAs respond to **OUT**s much more slowly than to display memory writes.)* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Be aware that as nifty as Mode X hardware-assisted masked copying is, +> whether or not it's actually faster than software-only masked or +> transparent copying depends upon the processor and the video adapter. +> The advantage of Mode X masked copying is the 32-bit parallelism; the +> disadvantages are the need to read display memory and the need to +> perform an **OUT** for every four pixels. (**OUT** is a slow 486/Pentium +> instruction, and most VGAs respond to **OUT**s much more slowly than to +> display memory writes.) \ No newline at end of file diff --git a/51-01.md b/51-01.md index ce2089a..94bebfd 100644 --- a/51-01.md +++ b/51-01.md @@ -29,9 +29,11 @@ This involves perspective, shading, proper handling of hidden surfaces, and rapid and smooth screen updates; the whole deal is considerably more difficult to pull off on a PC than 2-D animation. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In some senses, however, 3-D animation is easier than 2-D. Because there's more going on in 3-D animation, the eye and brain tend to make more assumptions, and so are more apt to see what they expect to see, rather than what's actually there.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> In some senses, however, 3-D animation is easier than 2-D. Because +> there's more going on in 3-D animation, the eye and brain tend to make +> more assumptions, and so are more apt to see what they expect to see, +> rather than what's actually there. If you're piloting a (virtual) ship through a field of thousands of asteroids at high speed, you're unlikely to notice if the more distant diff --git a/56-03.md b/56-03.md index 3c613d3..375a156 100644 --- a/56-03.md +++ b/56-03.md @@ -25,9 +25,18 @@ need the complete X-Sharp library to see texture mapping in action, but Listings 56.1 and 56.2 are the actual texture mapping code in its entirety. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Here's a major tip: DDA texture mapping looks best on fast-moving surfaces, where the eye doesn't have time to pick nits with the shearing and aliasing that's an inevi table by-product of such a crude approach. Compile DEMO1 from the X-Sharp archive in this chapter's subdirectory of the listings disk, and run it. The initial display looks okay, but certainly not great, because the rotational speed is so slow. Now press the S key a few times to speed up the rotation and flip between different rotation axes. I think you'll be amazed at how much better DDA texture mapping looks at high speed. This technique would be great for mapping textures onto hurtling asteroids or jets, but would come up short for slow, finely detailed movements.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Here's a major tip: DDA texture mapping looks best on fast-moving +> surfaces, where the eye doesn't have time to pick nits with the shearing +> and aliasing that's an inevi table by-product of such a crude approach. +> Compile DEMO1 from the X-Sharp archive in this chapter's subdirectory of +> the listings disk, and run it. The initial display looks okay, but +> certainly not great, because the rotational speed is so slow. Now press +> the S key a few times to speed up the rotation and flip between +> different rotation axes. I think you'll be amazed at how much better DDA +> texture mapping looks at high speed. This technique would be great for +> mapping textures onto hurtling asteroids or jets, but would come up +> short for slow, finely detailed movements. **LISTING 56.1 L56-1.C** diff --git a/57-01.md b/57-01.md index 0ba3019..5c442cd 100644 --- a/57-01.md +++ b/57-01.md @@ -68,9 +68,13 @@ VGA, I found that the faster rotation was too fast! The ball spun so rapidly that the eye couldn't blend successive images together into continuous motion, much like watching a badly flickering movie. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *So the second lesson is that either too little or too much speed can destroy the illusion. Unless you're antialiasing, you need to tune the shifting of your images so that they're in the "sweet spot" of apparent motion, in which the eye is willing to ignore the jumping and aliasing, and blend the images together into continuous motion. Only experience can give you a feel for that sweet spot.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> So the second lesson is that either too little or too much speed can +> destroy the illusion. Unless you're antialiasing, you need to tune the +> shifting of your images so that they're in the "sweet spot" of apparent +> motion, in which the eye is willing to ignore the jumping and aliasing, +> and blend the images together into continuous motion. Only experience +> can give you a feel for that sweet spot. #### Fixed-Point Arithmetic, Redux {#Heading4} diff --git a/58-03.md b/58-03.md index 0d6c416..d50514a 100644 --- a/58-03.md +++ b/58-03.md @@ -16,9 +16,19 @@ scanning it out left to right; it is only in conjunction with the hardware organization of Mode X that the scanning direction matters in the least. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *That's what Zen programming is all about, though; tying together two pieces of seemingly unrelated information to good effect—and that's what I had failed to do. Like Robert Heinlein—like all of us—I had viewed the world through a filter composed of my ingrained assumptions, and one of those assumptions, based on all my past experience, was that pixel processing proceeds left to right. Eventually, I might have come up with Chris's approach; but I would only have come up with it when and if I relaxed and stepped back a little, and allowed myself—almost dared myself—to think of it. When you're optimizing, be sure to leave quiet, nondirected time in which to conjure up those less obvious solutions, and periodically try to figure out what assumptions you're making—and then question them!* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> That's what Zen programming is all about, though; tying together two +> pieces of seemingly unrelated information to good effect—and that's what +> I had failed to do. Like Robert Heinlein—like all of us—I had viewed the +> world through a filter composed of my ingrained assumptions, and one of +> those assumptions, based on all my past experience, was that pixel +> processing proceeds left to right. Eventually, I might have come up with +> Chris's approach; but I would only have come up with it when and if I +> relaxed and stepped back a little, and allowed myself—almost dared +> myself—to think of it. When you're optimizing, be sure to leave quiet, +> nondirected time in which to conjure up those less obvious solutions, +> and periodically try to figure out what assumptions you're making—and +> then question them! ![**Figure 58.3**  *Texture mapping a single vertical column.*](images/58-03.jpg) diff --git a/59-03.md b/59-03.md index 2683d08..509edaf 100644 --- a/59-03.md +++ b/59-03.md @@ -130,6 +130,10 @@ of the left and right vertices indicates which way the wall is facing. } } - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Be aware that BSP trees can often be made smaller and more efficient by detecting collinear surfaces (like aligned wall segments) and generating only one BSP node for each collinear set, with the collinear surfaces stored in, say, a linked list attached to that node. Collinear surfaces partition space identically and can't occlude one another, so it suffices to generate one splitting node for each collinear set.* - ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Be aware that BSP trees can often be made smaller and more efficient by +> detecting collinear surfaces (like aligned wall segments) and generating +> only one BSP node for each collinear set, with the collinear surfaces +> stored in, say, a linked list attached to that node. Collinear surfaces +> partition space identically and can't occlude one another, so it +> suffices to generate one splitting node for each collinear set. \ No newline at end of file diff --git a/59-05.md b/59-05.md index 56152ac..e55b9c0 100644 --- a/59-05.md +++ b/59-05.md @@ -32,10 +32,29 @@ they are unfamiliar, they abandon the perfectly good model they used before and try to rederive it in the new context by the seat of their pants. - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Here's a secret when you're faced with a situation like this: Step back and get a clear picture of what your code has to do. Omit no steps. You should build a model that is so consistent and solid that you can instantly answer any question about how the code should behave in any situation. For example, my interviewees often decide, by trial and error, that there are two distinct types of right children: Right children visited after popping back to visit a node after the left subtree has been visited, and right children visited after descending to a node that has no left child. This makes the traversal code a mass of special cases, each of which has to be detected by the programmer by trying out scenarios. Worse, you can never be sure with this approach that you've caught all the special cases.* - *The alternative is to develop and apply a unifying model. There aren't really two types of right children; the rule is that all right children are visited after their parents are visited, period. The presence or absence of a left child is irrelevant. The possibility that a right child may be reached via different code paths depending on the presence of a left child does not affect the overall model. While this distinction may seem trivial it is in fact crucial, because if you have the model down cold, you can always tell if the implementation is correct by comparing it with the model.* - ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Here's a secret when you're faced with a situation like this: Step back +> and get a clear picture of what your code has to do. Omit no steps. You +> should build a model that is so consistent and solid that you can +> instantly answer any question about how the code should behave in any +> situation. For example, my interviewees often decide, by trial and +> error, that there are two distinct types of right children: Right +> children visited after popping back to visit a node after the left +> subtree has been visited, and right children visited after descending to +> a node that has no left child. This makes the traversal code a mass of +> special cases, each of which has to be detected by the programmer by +> trying out scenarios. Worse, you can never be sure with this approach +> that you've caught all the special cases. +> +> The alternative is to develop and apply a unifying model. There aren't +> really two types of right children; the rule is that all right children +> are visited after their parents are visited, period. The presence or +> absence of a left child is irrelevant. The possibility that a right +> child may be reached via different code paths depending on the presence +> of a left child does not affect the overall model. While this +> distinction may seem trivial it is in fact crucial, because if you have +> the model down cold, you can always tell if the implementation is +> correct by comparing it with the model. #### Measure and Learn {#Heading10} diff --git a/61-03.md b/61-03.md index 875d047..8066f3f 100644 --- a/61-03.md +++ b/61-03.md @@ -40,9 +40,13 @@ vectors that lie within the surface. This means that we can generate the screenspace normals we need by taking the cross product of two adjacent polygon edges, as shown in Figure 61.4. - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In fact, we can cull with only one-third the work needed to generate a full cross product; because we're interested only in the sign of the z component of the normal, we can skip entirely calculating the x and y components. The only caveat is to be careful that neither edge you choose is zero-length and that the edges aren't collinear, because the dot product can't produce a normal in those cases.* - ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> In fact, we can cull with only one-third the work needed to generate a +> full cross product; because we're interested only in the sign of the z +> component of the normal, we can skip entirely calculating the x and y +> components. The only caveat is to be careful that neither edge you +> choose is zero-length and that the edges aren't collinear, because the +> dot product can't produce a normal in those cases. ![**Figure 61.4**  *How the cross product of polygon edge vectors generates a polygon normal.*](images/61-04.jpg) diff --git a/61-04.md b/61-04.md index de27fc3..d8a490f 100644 --- a/61-04.md +++ b/61-04.md @@ -106,9 +106,16 @@ Figure 61.8. In 3-D, this involves three dot products per point, one to project the point onto each axis. Translation can be done separately from rotation by simple addition. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Rotation by projection is exactly the same as rotation via matrix multiplication; in fact, the rows of a rotation matrix are the orthogonal unit vectors pointing along the new axes. Rotation by projection buys us no technical advantages, so that's not what's important here; the key is that the concept of rotation by projection, together with a separate translation step, gives us a new way to look at transformation that I, for one, find easier to visualize and experiment with. A new frame of reference for how we think about 3-D frames of reference, if you will.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> Rotation by projection is exactly the same as rotation via matrix +> multiplication; in fact, the rows of a rotation matrix are the +> orthogonal unit vectors pointing along the new axes. Rotation by +> projection buys us no technical advantages, so that's not what's +> important here; the key is that the concept of rotation by projection, +> together with a separate translation step, gives us a new way to look at +> transformation that I, for one, find easier to visualize and experiment +> with. A new frame of reference for how we think about 3-D frames of +> reference, if you will. Three things I've learned over the years are that it never hurts to learn a new way of looking at things, that it helps to have a clearer, diff --git a/62-01.md b/62-01.md index 242d614..1893658 100644 --- a/62-01.md +++ b/62-01.md @@ -61,9 +61,15 @@ work yet, so I didn't waste any time due to my faulty assumption that should have done experiments until I was sure I knew what was going on before drawing any conclusions and acting on them. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In general, make it a point not to fall into a tightly focused rut; stay loose and think of alternative possibilities and new approaches, and always, always, always keep asking questions. It'll pay off big in the long run. If I hadn't indulged my curiosity by running the Pentium counter test on the copy to the screen, even though there was no specific reason to do so, I would never have discovered the **memcpy()** problem—and by so doing I doubled the performance of the entire program in five minutes, a rare accomplishment indeed.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> In general, make it a point not to fall into a tightly focused rut; stay +> loose and think of alternative possibilities and new approaches, and +> always, always, always keep asking questions. It'll pay off big in the +> long run. If I hadn't indulged my curiosity by running the Pentium +> counter test on the copy to the screen, even though there was no +> specific reason to do so, I would never have discovered the **memcpy()** +> problem—and by so doing I doubled the performance of the entire program +> in five minutes, a rare accomplishment indeed. By the way, I have found the Pentium's performance counters to be very useful in of information on the performance counters and other aspects diff --git a/64-01.md b/64-01.md index e26976b..377fef9 100644 --- a/64-01.md +++ b/64-01.md @@ -50,9 +50,16 @@ the CPU wanted to access display memory, pixels could come from the FIFO while the CPU was serviced immediately. That did indeed help performance—but not as much as Tom's write FIFO. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *What we have here is as neat a parable about the nature of creative design as one could hope to find. The scrap of news about Paradise's chip contained almost no actual information, but it forced Tom to push past the limits he had unconsciously set in coming up with his original design. And, in the end, I think that the single most important element of great design, whether it be hardware, software, or any creative endeavor, is precisely what the Paradise news triggered in Tom: the ability to detect the limits you have built into the way you think about your design, and then transcend those limits.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> What we have here is as neat a parable about the nature of creative +> design as one could hope to find. The scrap of news about Paradise's +> chip contained almost no actual information, but it forced Tom to push +> past the limits he had unconsciously set in coming up with his original +> design. And, in the end, I think that the single most important element +> of great design, whether it be hardware, software, or any creative +> endeavor, is precisely what the Paradise news triggered in Tom: the +> ability to detect the limits you have built into the way you think about +> your design, and then transcend those limits. The problem, of course, is how to go about transcending limits you don't even know you've imposed. There's no formula for success, but two diff --git a/64-04.md b/64-04.md index 724a10b..0cb30f1 100644 --- a/64-04.md +++ b/64-04.md @@ -66,9 +66,15 @@ they've been designed. But the process of getting there requires incredible persistence and a willingness to try lots of different ideas until the right one falls into place, as happened here. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *My friend Chris Hecker has a theory that all approaches work out to the same thing in the end, since they all reflect the same underlying state and functionality. In terms of underlying theory, I've found that to be true; whether you do perspective texture mapping with a divide or with incremental hyperbolic calculations, the numbers do exactly the same thing. When it comes to implementation, however, my experience is that simply time-shifting an approach, or matching hardware capabilities better, or caching can make an astonishing difference.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> My friend Chris Hecker has a theory that all approaches work out to the +> same thing in the end, since they all reflect the same underlying state +> and functionality. In terms of underlying theory, I've found that to be +> true; whether you do perspective texture mapping with a divide or with +> incremental hyperbolic calculations, the numbers do exactly the same +> thing. When it comes to implementation, however, my experience is that +> simply time-shifting an approach, or matching hardware capabilities +> better, or caching can make an astonishing difference. My friend Terje Mathisen likes to say that "almost all programming can be viewed as an exercise in caching," and that's exactly what John did. diff --git a/66-02.md b/66-02.md index 05d4cf6..cb906bf 100644 --- a/66-02.md +++ b/66-02.md @@ -16,9 +16,12 @@ run as fast as possible, but we also want the difference in drawing speed between the average scene and the slowest-drawing scene to be as small as possible. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *It does little good to average 30 frames per second if 10 percent of the scenes draw at 5 fps, because the jerkiness in those scenes will be extremely obvious by comparison with the average scene, and highly objectionable. It would be better to average 15 fps 100 percent of the time, even though the average drawing speed is only half as much.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> ![](images/i.jpg) +> It does little good to average 30 frames per second if 10 percent of the +> scenes draw at 5 fps, because the jerkiness in those scenes will be +> extremely obvious by comparison with the average scene, and highly +> objectionable. It would be better to average 15 fps 100 percent of the +> time, even though the average drawing speed is only half as much. The precalculated PVS was an important step toward both faster and more level performance, because it eliminated the need to identify visible diff --git a/70-03.md b/70-03.md index 7192895..c2aa5e7 100644 --- a/70-03.md +++ b/70-03.md @@ -120,6 +120,8 @@ Leveling is very important, because cases where a game slows down to the point of being unplayable dictate game and level design, and the fewer constraints placed on design, the better. - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *A corollary is that best case performance can be seductively misleading; it's a great feeling to see a scene running at 30 or even 60 frames per second, but if the bulk of the game runs at 15 fps, those best cases are just going to make the rest of the game look worse.* - ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> A corollary is that best case performance can be seductively misleading; +> it's a great feeling to see a scene running at 30 or even 60 frames per +> second, but if the bulk of the game runs at 15 fps, those best cases are +> just going to make the rest of the game look worse. \ No newline at end of file diff --git a/70-08.md b/70-08.md index a92a0e9..ad12bc1 100644 --- a/70-08.md +++ b/70-08.md @@ -49,9 +49,14 @@ of time needed to make the interpolation possible. This illustrates a common tradeoff, which is that reduced latency often makes for rougher play. - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Reduced latency also often makes for more frustrating play. It's actually not hard to reduce the latency perceived by the player, but many of the approaches that reduce latency introduce the potential for paradoxes that can be quite distracting and annoying. For example, a player may see a rocket go by, and think they've dodged it, only to find themselves exploding a second later as the difference of opinion between his simulation and the other simulation is resolved to his detriment.* - ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> ![](images/i.jpg) +> Reduced latency also often makes for more frustrating play. It's +> actually not hard to reduce the latency perceived by the player, but +> many of the approaches that reduce latency introduce the potential for +> paradoxes that can be quite distracting and annoying. For example, a +> player may see a rocket go by, and think they've dodged it, only to find +> themselves exploding a second later as the difference of opinion between +> his simulation and the other simulation is resolved to his detriment. Worse, QTest1 was prone to frequent hitching over all but the best connections, because it was built around reliable packet delivery (TCP)