diff --git a/01-02.md b/01-02.md index 2e58a67..b0bbaac 100644 --- a/01-02.md +++ b/01-02.md @@ -14,20 +14,20 @@ pages: 007-009 We've got the following rules for creating high-performance software: -- Know where you're going (understand the objective of the software). -- Make a big map (have an overall program design firmly in mind, so + * Know where you're going (understand the objective of the software). + * Make a big map (have an overall program design firmly in mind, so the various parts of the program and the data structures work well together). -- Make lots of little maps (design an algorithm for each separate part + * Make lots of little maps (design an algorithm for each separate part of the overall design). -- Know the territory (understand exactly how the computer carries out + * Know the territory (understand exactly how the computer carries out each task). -- Know when it matters (identify the portions of your programs where + * Know when it matters (identify the portions of your programs where performance matters, and don't waste your time optimizing the rest). -- Always consider the alternatives (don't get stuck on a single + * Always consider the alternatives (don't get stuck on a single approach; odds are there's a better way, if you're clever and inventive enough). -- Know how to turn on the juice (optimize the code as best you know + * Know how to turn on the juice (optimize the code as best you know how when it *does* matter). Making rules is easy; the hard part is figuring out how to apply them in diff --git a/01-04.md b/01-04.md index 21d789d..ed8dba1 100644 --- a/01-04.md +++ b/01-04.md @@ -105,17 +105,17 @@ whizzing through the data in the buffer inside a single loop. There are four reasons that many programmers would give for not trying to improve on Listing 1.4: -**1.**  The code is already fast enough. - -**2.**  The code works, and some people are content with code that -works, even when it's slow enough to be annoying. - -**3.**  The C library is written in optimized assembly, and it's likely -to be faster than any code that the average programmer could write to -perform essentially the same function. - -**4.**  The C library conveniently handles the buffering of file data, -and it would be a nuisance to have to implement that capability. + 1. The code is already fast enough. + + 2. The code works, and some people are content with code that + works, even when it's slow enough to be annoying. + + 3. The C library is written in optimized assembly, and it's likely + to be faster than any code that the average programmer could write to + perform essentially the same function. + + 4. The C library conveniently handles the buffering of file data, + and it would be a nuisance to have to implement that capability. I'll ignore the first reason, both because performance is no longer an issue if the code is fast enough and because the current application diff --git a/04-01.md b/04-01.md index 330dd46..834a719 100644 --- a/04-01.md +++ b/04-01.md @@ -115,10 +115,10 @@ indeed. The major cycle-eaters are: -- The 8088's 8-bit external data bus. -- The prefetch queue. -- Dynamic RAM refresh. -- Wait states, notably display memory wait states and, in the AT and + * The 8088's 8-bit external data bus. + * The prefetch queue. + * Dynamic RAM refresh. + * Wait states, notably display memory wait states and, in the AT and 80386 computers, system memory wait states. The locations of these cycle-eaters in the primordial 8088-based PC are diff --git a/04-10.md b/04-10.md index 3128d55..086954c 100644 --- a/04-10.md +++ b/04-10.md @@ -94,15 +94,15 @@ all become clear from further reading, especially once you study, time, and tune code that you have written yourself. What's really important is that you come away from this chapter understanding that on the 8088: -- The 8-bit bus cycle-eater causes each access to a word-sized operand + * The 8-bit bus cycle-eater causes each access to a word-sized operand to be 4 cycles longer than an equivalent access to a byte-sized operand. -- The prefetch queue cycle-eater can cause instruction execution times + * The prefetch queue cycle-eater can cause instruction execution times to be as much as four times longer than the officially documented cycle times. -- The DRAM refresh cycle-eater slows most PC code, with performance + * The DRAM refresh cycle-eater slows most PC code, with performance reductions ranging as high as 8.33 percent. -- The display adapter cycle-eater typically doubles and can more than + * The display adapter cycle-eater typically doubles and can more than triple the length of the standard 4-cycle access to display memory, with intensive display memory access suffering most. diff --git a/08-02.md b/08-02.md index a6b5275..d8d74e3 100644 --- a/08-02.md +++ b/08-02.md @@ -119,14 +119,14 @@ More on this shortly. To recap, here are some things to look for when striving to convert C code into optimized assembly language: -- Move the entire performance-critical section into a single assembly + * Move the entire performance-critical section into a single assembly language function. -- Don't use calls or stack frame accesses inside the critical code, if + * Don't use calls or stack frame accesses inside the critical code, if possible, and avoid unnecessary memory accesses of any kind. -- Change segments as infrequently as possible. -- Optimize in terms of what assembly does well, *not* in terms of + * Change segments as infrequently as possible. + * Optimize in terms of what assembly does well, *not* in terms of fine-tuning compiled C code. -- Change the rules to the benefit of assembly, if necessary; for + * Change the rules to the benefit of assembly, if necessary; for example, reorganize data structto allow efficient assembly language processing. diff --git a/11-03.md b/11-03.md index fb6ce40..4328071 100644 --- a/11-03.md +++ b/11-03.md @@ -29,22 +29,22 @@ outrun even zero—5 cycles longer than the official execution time.) To summarize: -- Memory-accessing instructions don't run at their official speeds on + * Memory-accessing instructions don't run at their official speeds on non-zero-wait-state 286/386 computers. -- The prefetch queue cycle-eater reduces performance on 286/386 + * The prefetch queue cycle-eater reduces performance on 286/386 computers, particularly when non-zero-wait-state memory is used. -- Branches often execute at less than their rated speeds on the 286 + * Branches often execute at less than their rated speeds on the 286 and 386 since the prefetch queue is emptied. -- The extent to which the prefetch queue and wait states affect + * The extent to which the prefetch queue and wait states affect performance varies from one 286/386 computer to another, making precise optimization impossible. What's to be learned from all this? Several things: -- Keep your instructions short. -- Keep it in the registers; avoid memory, since memory generally can't + * Keep your instructions short. + * Keep it in the registers; avoid memory, since memory generally can't keep up with the processor. -- Don't jump. + * Don't jump. Of course, those are exactly the rules that apply to 8088 optimization as well. Isn't it convenient that the same general rules apply across diff --git a/16-01.md b/16-01.md index 5de4a73..cac8a69 100644 --- a/16-01.md +++ b/16-01.md @@ -21,25 +21,25 @@ listing the 10 leading concerns of corporate buyers when it comes to C++. Boiled down, the list looked like this, in order of descending importance to buyers: -**1.**  Debugging - -**2.**  Documentation - -**3.**  Windows development tools - -**4.**  High-level Windows support - -**5.**  Class library - -**6.**  Development cycle efficiency - -**7.**  Object-oriented development aids - -**8.**  Programming management aids - -**9.**  Online help - -**10.**  Windows development cycle automation + 1. Debugging + + 2. Documentation + + 3. Windows development tools + + 4. High-level Windows support + + 5. Class library + + 6. Development cycle efficiency + + 7. Object-oriented development aids + + 8. Programming management aids + + 9. Online help + + 10. Windows development cycle automation Is something missing here? You bet your maximum *gluteus* something's missing—nowhere on that list is there so much as one word about how fast diff --git a/17-01.md b/17-01.md index 4a05784..7286827 100644 --- a/17-01.md +++ b/17-01.md @@ -73,9 +73,9 @@ vertically, and four diagonally. For each succeeding generation of cells, the game logic determines whether each cell will be on or off according to the following rules: -- If a cell is on and has either two or three neighbors that are on in + * If a cell is on and has either two or three neighbors that are on in the current generation, it stays on; otherwise, the cell turns off. -- If a cell is off and has exactly three "on" neighbors in the current + * If a cell is off and has exactly three "on" neighbors in the current generation, it turns on; otherwise, it stays off. That's all the rules there are—but they give rise to an astonishing variety of forms, including patterns that spin, march across the screen, and diff --git a/17-06.md b/17-06.md index 4d304ee..7c0007c 100644 --- a/17-06.md +++ b/17-06.md @@ -17,11 +17,11 @@ Listing 17.4—and it's still in C++. How is this possible? Here are some hints: -- After a few dozen generations, most of the cellmap consists of cells + * After a few dozen generations, most of the cellmap consists of cells in the off state. -- There are many possible cellmap representations other than one + * There are many possible cellmap representations other than one bit-per-pixel. -- Cells change state relatively infrequently. + * Cells change state relatively infrequently. ### Bringing In the Right Brain {#Heading8} diff --git a/17-08.md b/17-08.md index 2601af2..31a9430 100644 --- a/17-08.md +++ b/17-08.md @@ -59,20 +59,20 @@ updating the cellmap was what I wanted people to concentrate on. Here are the rules I laid down for the challenge: -- Readers could modify any code in Listing 17.5, except the main loop, + * Readers could modify any code in Listing 17.5, except the main loop, as well as change the cell map representation any way they liked. However, the code had to produce exactly the same output as Listing 17.5 under all circumstances in order to be eligible to win. -- Engine code had to be less than 400 lines long *in total,* excluding + * Engine code had to be less than 400 lines long *in total,* excluding the video-related code shown in Listing 17.2. -- Submissions had to compile/assemble with Borland C++ (in either C++ + * Submissions had to compile/assemble with Borland C++ (in either C++ or C mode, as desired) and/or TASM. -- All submissions had to handle cellmaps at least 200x200 in size. -- Assembly language could of course be used to speed up any part of + * All submissions had to handle cellmaps at least 200x200 in size. + * Assembly language could of course be used to speed up any part of the program. C rather than C++ was legal as well, so long as entered implementations produced the same results as Listing 17.5 and 17.2 together and were less than 400 lines long. -- All entries would be timed on the same 33 MHz 486 with a 256K + * All entries would be timed on the same 33 MHz 486 with a 256K external cache. That was the challenge I put to the readers. Little did I realize the diff --git a/38-02.md b/38-02.md index 99d951b..12a9d34 100644 --- a/38-02.md +++ b/38-02.md @@ -21,17 +21,17 @@ to handle points that are exactly on the boundary, and points that lie at vertices, so that those points are drawn once and only once. To deal with that, we're going to adopt the following rules: -- Points located exactly on nonhorizontal edges are drawn only if the + * Points located exactly on nonhorizontal edges are drawn only if the interior of the polygon is directly to the right (left edges are drawn, right edges aren't). ![](images/38-03.jpg)\ **Figure 38.3**  *The adjacent polygons problem.* -- Points located exactly on horizontal edges are drawn only if the + * Points located exactly on horizontal edges are drawn only if the interior of the polygon is directly below them (horizontal top edges are drawn, horizontal bottom edges aren't). -- A vertex is drawn only if all lines ending at that point meet the + * A vertex is drawn only if all lines ending at that point meet the above conditions (no right or bottom edges end at that point). All edges of a polygon except those that are flat tops or flat bottoms diff --git a/39-01.md b/39-01.md index d240a5c..ed9f427 100644 --- a/39-01.md +++ b/39-01.md @@ -105,11 +105,11 @@ polygon-filling code that's fast enough to be truly usable. Our original polygon filling code involved three major tasks, each performed by a separate function: -- Tracing each polygon edge to generate a coordinate list (performed + * Tracing each polygon edge to generate a coordinate list (performed by the function **ScanEdge);** -- Drawing the scanned-out horizontal lines that constitute the filled + * Drawing the scanned-out horizontal lines that constitute the filled polygon (**DrawHorizontalLineList** ); and -- Characterizing the polygon and coordinating the tracing and drawing + * Characterizing the polygon and coordinating the tracing and drawing (**FillConvexPolygon** ). The amount of time that the previous chapter's sample program spent in diff --git a/40-02.md b/40-02.md index 6ca0ad6..3e1c0e3 100644 --- a/40-02.md +++ b/40-02.md @@ -41,25 +41,25 @@ Y coordinates, error terms and error term adjustments, lengths, and directions of X movement for each edge. Once the GET is built, we'll do the following: -**1.**  Set the current Y coordinate to the Y coordinate of the first -edge in the GET. + 1. Set the current Y coordinate to the Y coordinate of the first + edge in the GET. -**2.**  Move all edges with the current Y coordinate from the GET to the -AET, removing them from the GET and maintaining the X-sorted order of -the AET. + 2. Move all edges with the current Y coordinate from the GET to the + AET, removing them from the GET and maintaining the X-sorted order of + the AET. -**3.**  Draw all odd-to-even spans in the AET at the current Y -coordinate. + 3. Draw all odd-to-even spans in the AET at the current Y + coordinate. -**4.**  Count down the lengths of all edges in the AET, removing any -edges that are done, and advancing the X coordinates of all remaining -edges in the AET by one scan line. + 4. Count down the lengths of all edges in the AET, removing any + edges that are done, and advancing the X coordinates of all remaining + edges in the AET by one scan line. -**5.**  Sort the AET in order of ascending X coordinate. + 5. Sort the AET in order of ascending X coordinate. -**6.**  Advance the current Y coordinate by one scan line. - -**7.**  If either the AET or GET isn't empty, go to step 2. + 6. Advance the current Y coordinate by one scan line. + + 7. If either the AET or GET isn't empty, go to step 2. ![](images/40-03.jpg)\ **Figure 40.3**  *The global and active edge tables as linked lists.* diff --git a/45-06.md b/45-06.md index 701e777..aa30608 100644 --- a/45-06.md +++ b/45-06.md @@ -40,22 +40,22 @@ Serge didn't care to do all that bookkeeping in his animation applications, so he came up with the following approach, which I've reworded, amplified, and slightly modified in the summary here: -**1.**  Set the start address to display page 0. + 1. Set the start address to display page 0. -**2.**  Draw to page 1. + 2. Draw to page 1. -**3.**  Set the start address to display page 1 (the newly drawn page), -then wait for the leading edge of vertical sync, at which point the page -has flipped and it's safe to modify page 0. + 3. Set the start address to display page 1 (the newly drawn page), + then wait for the leading edge of vertical sync, at which point the page + has flipped and it's safe to modify page 0. -**4.**  Copy, via the latches, from page 1 to page 0 the areas that -changed from the previous screen to the current one. + 4. Copy, via the latches, from page 1 to page 0 the areas that + changed from the previous screen to the current one. -**5.**  Set the start address to display page 0, which is now identical -to page 1, then wait for the leading edge of vertical sync, at which -point the page has flipped and it's safe to modify page 1. + 5. Set the start address to display page 0, which is now identical + to page 1, then wait for the leading edge of vertical sync, at which + point the page has flipped and it's safe to modify page 1. -**6.**  Go to step 2. + 6. Go to step 2. The great benefit of Serge's approach is that the only page that is ever actually drawn to (as opposed to being block-copied to) is page 1. Only @@ -74,14 +74,14 @@ less-experienced animation programmers. An interesting variation on Serge's approach doesn't page flip nor wait for vertical sync: -**1.**  Set the start address to display page 0. + 1. Set the start address to display page 0. -**2.**  Draw to page 1. + 2. Draw to page 1. -**3.**  Copy, via the latches, the areas that changed from the last -screen to the current one from page 1 to page 0. + 3. Copy, via the latches, the areas that changed from the last + screen to the current one from page 1 to page 0. -**4.**  Go to step 2. + 4. Go to step 2. This approach totally eliminates page flipping, which can consume a great deal of time. The downside is that images may shear for one frame diff --git a/52-01.md b/52-01.md index 6698cfc..16f3113 100644 --- a/52-01.md +++ b/52-01.md @@ -79,12 +79,12 @@ code builds on the code from no fewer than five previous chapters. The code that's required in order to link this chapter's animation demo program is the following: -- Listing 50.1 from Chapter 50 (draw clipped line list); -- Listings 47.1 and 47.6 from Chapter 47 (Mode X mode set, rectangle + * Listing 50.1 from Chapter 50 (draw clipped line list); + * Listings 47.1 and 47.6 from Chapter 47 (Mode X mode set, rectangle fill); -- Listing 49.6 from Chapter 49; -- Listing 39.4 from Chapter 39 (polygon edge scan); and -- The **FillConvexPolygon( )** function from Listing 38.1 from Chapter + * Listing 49.6 from Chapter 49; + * Listing 39.4 from Chapter 39 (polygon edge scan); and + * The **FillConvexPolygon( )** function from Listing 38.1 from Chapter 38. Note that the **struct** keywords in **FillConvexPolygon( )** must be removed to reflect the switch to typedefs in the animation header file. diff --git a/59-06.md b/59-06.md index bef06ee..beeb0f6 100644 --- a/59-06.md +++ b/59-06.md @@ -40,17 +40,17 @@ measurably faster than Listing 59.2, but not even close to being The moral of this story (apart from it being a good idea to enable compiler optimization) is: -**1.**  Understand what you're doing, through and through. + 1. Understand what you're doing, through and through. -**2.**  Build a complete and consistent model in your head. + 2. Build a complete and consistent model in your head. -**3.**  Design from the principles that the model provides. + 3. Design from the principles that the model provides. -**4.**  Implement the design. + 4. Implement the design. -**5.**  Measure to learn what you've wrought. + 5. Measure to learn what you've wrought. -**6.**  Go back to step 1 and apply what you've just learned. + 6. Go back to step 1 and apply what you've just learned. With each iteration you'll dig deeper, learn more, and improve your ability to know where and how to focus your design and programming diff --git a/62-03.md b/62-03.md index 71ef943..57cf0cd 100644 --- a/62-03.md +++ b/62-03.md @@ -16,12 +16,12 @@ Conceptually rendering from a BSP tree really is that simple, but the implementation is a bit more complicated. The full rendering pipeline, as coordinated by **UpdateWorld()**, is this: -- Update the current location. -- Transform all wall endpoints into viewspace (the world as seen from + * Update the current location. + * Transform all wall endpoints into viewspace (the world as seen from the current location with the current viewing angle). -- Clip all walls to the view pyramid. -- Project wall vertices to screen coordinates. -- Walk the walls back to front, and for each wall that lies at least + * Clip all walls to the view pyramid. + * Project wall vertices to screen coordinates. + * Walk the walls back to front, and for each wall that lies at least partially in the view pyramid, perform backface culling (skip walls facing away from the viewer), and draw the wall if it's not culled.