![]() |
|
| Optimizing a drawing routine | ||||||||||
By Fredrik Mörk OK, this is my first
project that I’ve done with the eminent SineEngine and Blitter Object controls.
When I started out it was intended as a minimal implementation of the controls,
giving an idea of how easy they are to use. As I went along though, since they
really are easy to use, I ended up doing some optimizations work as well. So my
minimal implementation tutorial instead became a small tutorial on
optimizing
code. Still, the application is not really complicated. What does it do?
Basically it uses the SineEngine control to receive X and Y coordinated
traveling along a path, using these coordinated to draw lines that go back and
forth across the screen, leaving a fading trail after them. The lines are, of
course, drawn on a Blitter Object control. The sine settings are changed every
10 seconds. It also displays the current frame rate.
The interesting thing is that you can explore how some simple code optimizations affects performance, by switching them on and off. There are three things you can do:
The code could look something like this: For i = nLBound To nUBound bobMain.Buffer.Line (Points(i, nLBound).X, Points(i, nLBound).Y)-(Points(i, nLBound).X, Points(i, nLBound).Y) For j = 1 To NBR_OF_POINTS bobMain.Buffer.Line (Points(j - 1, i).X, Points(j - 1, i).Y)-(Points(j, i).X, Points(j, i).Y), lLineShades(j) Next j, i A total of five lines of code is all that is needed to make the drawing. Of course, we have already updated the Points array with correct coordinates and everything (this is done in cooperation with the SineEngine, and consists of four lines of code). So, as you can see, not a lot of code is needed. At this point I usually start to glance at the Windows API. So, I change the drawing commands into their GDI equivalents:
For i = nLBound To nUBound nRet = MoveToEx(lTargetHDc, Points(i, nLBound).X, Points(i, nLBound).Y, DummyPoint nRet = LineTo(lTargetHDc, Points(i, nLBound).X, Points(i, nLBound).Y) For j = 1 To NBR_OF_POINTS bobMain.Buffer.ForeColor = lLineShades(j) nRet = LineTo(lTargetHDc, Points(j, i).X, Points(j, i).Y) Next j, i This method results in more lines of code (eight to be exact), but also result in a 50% performance increase on my machine. So, just by using GDI commands, which are implemented pretty much in the same way as VB command, we earn 50% performance! But still I wasn’t happy… The sines that are sent to the SineEngine contains 40 balls, and each line has 40 points (or segments), so for each frame 1600 line segments are drawn. For each frame, the application will run the inner for-loop 1600 times. With the GDI version on my machine, it would be executed over 14000 times per second. Obviously this is the place to look for optimization possibilities. In code listing 2 above, the inner for-loop contains three lines of code. One of these is really essential; LineTo. Without this line of code, no line is draw. But just for testing purposes I commented that line out, in order to see how the frame rate was affected, and the result was that the frame rate increased by 50%. That’s not bad! OK, so what happens with the other line of code, the one that sets the color? Simple test; comment that line out, and make the LineTo execute; the frame rate increased by over 600%! That’s even better. So, the conclusion was that the code for setting the drawing color takes a lot more time that the drawing itself. And, of course, we really need to draw in the inner loop, since the lines won’t be visualized otherwise. The problem with the above code is that is presents the slowest drawing code available in this application, but is usually the first one you would write, since it’s very logical. For each line, you draw each line segment in order, from black to white. This means that for each frame, the application does two things 1600 times: it sets the colour to draw with, and it draws a line. Now, each line has 40 segments, in colors (or rather, shades) ranging from black to white. All lines share these properties. So there are only 40 shades of gray. What would happen if we, instead of drawing each line at a time, drew each color at a time? Code listing 2 is changed into this; For i = 1 To NBR_OF_POINTS bobMain.Buffer.ForeColor = lLineShades(i) For j = nLBound To nUBound nRet = MoveToEx(lTargetHDc, Points(i - 1, j).X, Points(i - 1, j).Y, DummyPoint) nRet = LineTo(lTargetHDc, Points(i, j).X, Points(i, j).Y) Next j, i The change is that I switched the for-loop positions, so that the one that was innermost before, now is the outer loop. So the work order is as follows:
Disregarding the full screen/window mode switch, the remaining four different
drawing code blocks in the application give the following performance (on my
computer):
|
||||||||||
|
Copyright © 2002 Lars-Håkan Jönsson |