Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Profiling old code

This thread is locked; no one can reply to it. rss feed Print
Profiling old code
ngiacomelli
Member #5,114
October 2004

I was digging around my old code and found my Mode7 engine. It's very slow and I'd really like to give it a speed jolt. The majority of slowdown comes with rendering (obviously):

1 
2inline void fast_putpixel(BITMAP *bmp, int x, int y, int color)
3{
4 ((long *)bmp->line[y])[x] = color;
5}
6 
7inline int fast_getpixel(BITMAP *bmp, int x, int y)
8{
9 return ((long *)bmp->line[y])[x];
10}
11 
12void render_camera(BITMAP *source, BITMAP *dest) {
13
14 blit (source, dest, 0, 0, 0, 0, dest->w, dest->h);
15 clear(source);
16
17}
18 
19void render_map (BITMAP *bmp, BITMAP *tile, fixed angle, fixed cx, fixed cy, CAMERA_PARAMS camera)
20{
21 int screen_x, screen_y;
22 
23 fixed distance, horizontal_scale, line_dx, line_dy, space_x, space_y;
24 
25 int mask_x = (tile->w - 1);
26 int mask_y = (tile->h - 1);
27
28 for (screen_y = 75; screen_y < bmp->h; screen_y++)
29 {
30
31 distance = fdiv (fmul (camera.z, camera.s_y), itofix (screen_y + camera.horizon));
32 horizontal_scale = fdiv (distance, camera.s_x);
33 
34 line_dx = fmul (-fsin(angle), horizontal_scale);
35 line_dy = fmul (fcos(angle), horizontal_scale);
36 
37 space_x = cx + fmul (distance, fcos(angle)) - bmp->w/2 * line_dx;
38 space_y = cy + fmul (distance, fsin(angle)) - bmp->w/2 * line_dy;
39 
40 for (screen_x = 0; screen_x < bmp->w; screen_x++)
41 {
42
43 /*if( screen_x == mouse_x && screen_y == mouse_y ) {
44 application.m_x = 160 - fixtoi (space_x);
45 application.m_y = 100 - fixtoi (space_y);
46 }*/
47
48 fast_putpixel (bmp, screen_x, screen_y,
49 (
50
51 fast_getpixel (tile,
52 fixtoi (space_x) & mask_x,
53 fixtoi (space_y) & mask_y
54 )
55 
56 )
57 );
58 
59 space_x += line_dx; space_y += line_dy;
60
61 }
62 
63 }
64
65 
66}

fast_getpixel and fast_putpixel take up most of the execution time, along with fixtoi. I'm really looking to try and optimize this section of code. I had briefly considered a jump to hardware acceleration. Would this be of much use? Most of my project is already written in C. How much of a headache would it be to introduce something like Openlayer? Would I see a big speed increase?

Any help on either optimization or otherwise is appreciated!

TeamTerradactyl
Member #7,733
September 2006
avatar

Why, specifically, are you needing individual pixel-writes? Are you using them for a particle system?

ngiacomelli
Member #5,114
October 2004

The code is based on an ancient (and legendary) Allegro-specific tutorial, which featured a Mode7 example that worked by grabbing and plotting individual pixels for the Mode7 projection.

Thomas Harte
Member #33
April 2000
avatar

Quote:

ast_getpixel and fast_putpixel take up most of the execution time, along with fixtoi.

They should take up most of the execution time — they're doing all the work! Your compiler might be missing one obvious optimisation though. You and I can see that screen_y doesn't change inside the innermost loop, but because you're using inline functions the compiler possibly can't do anything about that. Have you considered something more like the following?

1 int screen_x, screen_y;
2 
3 fixed distance, horizontal_scale, line_dx, line_dy, space_x, space_y;
4 
5 int mask_x = (tile->w - 1);
6 int mask_y = (tile->h - 1);
7
8 for (screen_y = 75; screen_y < bmp->h; screen_y++)
9 {
10
11 distance = fdiv (fmul (camera.z, camera.s_y), itofix (screen_y + camera.horizon));
12 horizontal_scale = fdiv (distance, camera.s_x);
13 
14 line_dx = fmul (-fsin(angle), horizontal_scale);
15 line_dy = fmul (fcos(angle), horizontal_scale);
16 
17 space_x = cx + fmul (distance, fcos(angle)) - bmp->w/2 * line_dx;
18 space_y = cy + fmul (distance, fsin(angle)) - bmp->w/2 * line_dy;
19 
20 long *targetptr = (long *)bmp->line[screen_y];
21 
22 for (screen_x = 0; screen_x < bmp->w; screen_x++)
23 {
24
25 *targetptr++ =
26 (
27
28 fast_getpixel (tile,
29 fixtoi (space_x) & mask_x,
30 fixtoi (space_y) & mask_y
31 )
32 
33 )
34 )
35 
36 space_x += line_dx; space_y += line_dy
37 }

Also you might shave a few cycles by dumping fixtoi in favour of some simple >> 16s. fixtoi is bound to do something more like (x + 32768) >> 16 or some other rounding method, whereas truncation isn't going to be noticeably different for your application. I think the speed-ups are likely to be insubstantial though, at best.

Quote:

How much of a headache would it be to introduce something like Openlayer? Would I see a big speed increase?

I can't answer the headache question, but I can tell you the speed difference would be gigantic. A Mode-7 floor that isn't using a tilemap like yours can be done in a single quad, which the GPU will do for you.

ngiacomelli
Member #5,114
October 2004

Quote:

Also you might shave a few cycles by dumping fixtoi in favour of some simple >> 16s. fixtoi is bound to do something more like (x + 32768) >> 16 or some other rounding method, whereas truncation isn't going to be noticeably different for your application.

I've never done any bitshifting (I believe that's the terminology). Could you explain?

Also, I'm talking headache in terms of: updating my existing implementation, setting up OpenGL and writing code for it (having never done so).

TeamTerradactyl
Member #7,733
September 2006
avatar

Bitshifting:

int a = 128;
int b;

b = a >> 1; // 'b' now equals 128 / 2
b = a >> 2; // 'b' now equals (128 / 2) / 2
b = a >> 3; // 'b' now equals ((128 / 2) / 2) / 2

For each time you bit-shift to the right, you divide by two. It's called bit-shifting since it's noticable in binary:

a = 10000000

b = a >> 1:  01000000 // Shifted to the right by 1, so it now equals 64
b = a >> 2:  00100000 // Shifted to the right by 2, so it now equals 32
b = a >> 3:  00010000 // Shifted to the right by 3
b = a >> 7:  00000001 // Shifted to the right by 7, so it now equals 1
b = a >> 8:  00000000 // Now it equals 0

Now that I've written this, I hope you don't already know what this is and were looking for implementation instead of a "what's this?" ;D

Thomas Harte
Member #33
April 2000
avatar

Quote:

I've never done any bitshifting (I believe that's the terminology). Could you explain?

Bitshifting is the same as moving the decimal point with normal numbers. If you do << 1 then you move all the binary digits in your integer number one place to the left. If you do >> 1 then you move all the binary digits in your integer number one place to the right.

Because of the way binary works, if you do << 1 then you do the same thing as multiplying the number by 2. If you do << 2 then you do the same thing as multiplying the number by 4. That's exactly the same as the way that if you took an ordinary decimal number and moved all the digits one place to the left then you'd do the same thing as multiplying the number by 10. If you move all the digits two places to the left then you do the same thing as multiplying by 100.

Similarly >> 1 is like a divide by 2, >> 2 is like a divide by 4, >> 3 is like a divide by 8, etc. They're not identical this time because the rounding doesn't work. For example, in decimal, if you had the number 9 and you moved it one place to the right you'd have 0.9. If someone told you to round it to an integer you'd say the answer was 1. With shifting you get truncation, not rounding — i.e. any digits that fall off the end of the memory spot reserved for the integer are just forgotten.

Fixed numbers are really just integers that pretend to have a decimal point in the middle. Think of it like the difference in decimal between millimetres and metres. If you can only store integers and stick with one scale then you can only store distances like 1 metre, 2 metres, etc. If however you keep some measurements on a millimetre scale and convert them to metres when you need them then you can store with much greater precision.

Fixed numbers are exactly the same, except that in the realm of binary it's easier to divide a whole unit into units of 1/65536 rather than the 1/1000 in the milimetre example. So a fixed number is just an integer that uses a different scale.

So the correct way to convert a fixed to an integer is to divide by 65536, just as the correct way to convert from a millimetre to a metre is to divide by 1000. A time saving quick fix is to just shift the binary digits right by 16 places. You don't get exactly the right answer, but you save a tiny little bit of work.

In the millimetres example, if you just shifted then you'd see that 0 to 999 millimetres are considered to be 0 metres, 1000 to 1999 millimetres are considered to be 1 metre, etc. So a way to do the conversion correctly with shifting would be "add 500, then shift". That's no more than the rounding you learnt in school — if the digit after the one you are interested in is 5 or more then the one you are interested will go up by 1. If you aren't then it won't.

So another way to look at it is that all you do by just shifting rather than shift+adding is move the pixels on your floor texture map half a pixel to the right and half a pixel down. Which is barely any different.

ngiacomelli
Member #5,114
October 2004

Fantastic explanations. Thank you both. Thomas Harte, you seem to be something of an authority when it comes to this sort of stuff. There's been a slight speed increase with your suggestions. But I'm really looking to get the most out of this, as I possibly can. Is there a way of improving my rendering method, at all?

GullRaDriel
Member #3,861
September 2003
avatar

Avoiding a call to some function can help. So , I think that if you replace your fast_put/get pixel method directly by the only line inside the function, it should help, specially if you have a lots of call.

Also, you should profile your whole code and use something as gprof to see where exactly you loose some time.

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

orz
Member #565
August 2000

Quote:

For each time you bit-shift to the right, you divide by two. It's called bit-shifting since it's noticable in binary:

I just thought I'd point out a very minor detail here; when you divide by 2 using bitshifting, the rounding is towards negative infinity (ie 1 / 2 is 0, but -1 / 2 is -1), whereas when you divide using regular integer math the result is rounded towards 0 (1/2 = 0, -1/2 = 0).

Thomas Harte earlier posted this:

1 int screen_x, screen_y;
2 
3 fixed distance, horizontal_scale, line_dx, line_dy, space_x, space_y;
4 
5 int mask_x = (tile->w - 1);
6 int mask_y = (tile->h - 1);
7
8 for (screen_y = 75; screen_y < bmp->h; screen_y++)
9 {
10
11 distance = fdiv (fmul (camera.z, camera.s_y), itofix (screen_y + camera.horizon));
12 horizontal_scale = fdiv (distance, camera.s_x);
13 
14 line_dx = fmul (-fsin(angle), horizontal_scale);
15 line_dy = fmul (fcos(angle), horizontal_scale);
16 
17 space_x = cx + fmul (distance, fcos(angle)) - bmp->w/2 * line_dx;
18 space_y = cy + fmul (distance, fsin(angle)) - bmp->w/2 * line_dy;
19 
20 long *targetptr = (long *)bmp->line[screen_y];
21 
22 for (screen_x = 0; screen_x < bmp->w; screen_x++)
23 {
24
25 *targetptr++ =
26 (
27
28 fast_getpixel (tile,
29 fixtoi (space_x) & mask_x,
30 fixtoi (space_y) & mask_y
31 )
32 
33 )
34 )
35 
36 space_x += line_dx; space_y += line_dy
37 }

That's probably the biggest speed bump possible without doing something drastic (like switching color depths or using hardware acceleration).

He also mentioned replacing the fixtoi calls with simple left-shifts for further minute speedups. I'd just like to add that if you want identical rounding results you can add the 32768 to space_x and space_y prior to the loop. Furthermore, it's possible to optimize away the bitwise-and operators by left shifting space_x, line_dx, space_y, and line_dy prior to the loop in such a manner that integer overflow performed the same operation. However, the resulting speed improvement would probably be negligable, as bitwise-and is extremely fast; in fact, I don't actually recommend implementing this optimization, as the improvement should be almost too small to measure. The optimized version would look something like this:

1void render_map (BITMAP *bmp, BITMAP *tile, fixed angle, fixed cx, fixed cy, CAMERA_PARAMS camera)
2{
3 fixed distance, horizontal_scale, line_dx, line_dy, space_x, space_y;
4 int screen_x, screen_y;
5 
6 fixed distance, horizontal_scale, line_dx, line_dy, space_x, space_y;
7 
8 int mask_x = (tile->w - 1);
9 int mask_y = (tile->h - 1);
10 int shift_X = 32-fast_ilog2(tile->w);//replace with a #defined or enumed constant
11 int shift_Y = 32-fast_ilog2(tile->h);//if you know what size tile is at run-time
12
13 for (screen_y = 75; screen_y < bmp->h; screen_y++)
14 {
15
16 distance = fdiv (fmul (camera.z, camera.s_y), itofix (screen_y + camera.horizon));
17 horizontal_scale = fdiv (distance, camera.s_x);
18 
19 line_dx = fmul (-fsin(angle), horizontal_scale);
20 line_dy = fmul (fcos(angle), horizontal_scale);
21 
22 space_x = cx + fmul (distance, fcos(angle)) - bmp->w/2 * line_dx;
23 space_y = cy + fmul (distance, fsin(angle)) - bmp->w/2 * line_dy;
24 
25 line_dx <<= shift_X-16;
26 line_dy <<= shift_Y-16;
27 space_x += 32768; space_x <<= shift_X-16;
28 space_y += 32768; space_y <<= shift_Y-16;
29 long *targetptr = (long *)bmp->line[screen_y];
30 
31 for (screen_x = 0; screen_x < bmp->w; screen_x++)
32 {
33
34 *targetptr++ =
35 (
36
37 fast_getpixel (tile,
38 space_x >> shift_X,
39 space_y >> shift_Y
40 )
41 
42 )
43 )
44 
45 space_x += line_dx; space_y += line_dy
46 }
47int fast_ilog2(unsigned int x) {
48//returns 0 if x is 0 (there is no correct answer if x is zero)
49 int r = 0;
50 if (x & 0xFFFF0000) {r+=16; x >>= 16;}
51 if (x & 0xFF00) {r+=8; x >>= 8;}
52 if (x & 0xF0) {r+=4; x >>= 4;}
53 if (x & 0xC) {r+=2; x >>= 2;}
54 if (x & 0x2) {r+=1; x >>= 1;}
55 return r;
56}

note: that aproach will fail is the tile has a width or height of 1; use only 2x2 or large tiles with it

ngiacomelli
Member #5,114
October 2004

EDIT: Moved.

Go to: