Author Topic: Is saving a calculation as a variable always the way to go?  (Read 5306 times)

0 Members and 1 Guest are viewing this topic.

Offline Pixel_Outlaw

  • Pentium
  • *****
  • Posts: 1382
  • Karma: 83
    • View Profile
I'm now optimizing my OpenGL/blitzmax library.

I'm saving calculations done many times as variables. Is there ever a case where this is a bad idea?

Something like the following:

glbegin(gl_bosoms)
     for local i=0 to 35
          local subangle=i*10
          glvertex3f(cos(subangle)*radius,sin(subangle)*radius,0)
     next
glend

In this way the subangle is only calculated once rather that twice. Is this a smart use of a variable? I'm trying to really optimize this stuff so I won't look like a novice.
« Last Edit: October 06, 2007 by Pixel_Outlaw »
Challenge Trophies Won:

Offline taj

  • Bytes hurt
  • DBF Aficionado
  • ******
  • Posts: 4810
  • Karma: 189
  • Scene there, done that.
    • View Profile
glbegin(gl_bosoms)
     for local i=0 to 35
          local subangle=i*10
          glvertex3f(cos(subangle)*radius,sin(subangle)*radius,0)
     next
glend

 I'm trying to really optimize this stuff so I won't look like a novice.

In general you are right, its always the way to go. So for performance optimisation (as opposed to size) go with this rule. Its very unlikely to be worse, maybe even impossible.

In your specific example there are some optimisations. Forgive me if I make syntax errors , I'm not familiar with your language...

Code: [Select]
glbegin(gl_bosoms)
     local subangle=0
     for local i=0 to 35
          glvertex3f(cos(subangle)*radius,sin(subangle)*radius,0)
          i=i+10
     next
glend

Here the code is bigger...but its likely to be faster...as now you only add in the inner loop, not multiply. Another way to write this would be:

Code: [Select]
glbegin(gl_bosoms)
     for radius=0 to 350 step 10
          glvertex3f(cos(subangle)*radius,sin(subangle)*radius,0)
     next
glend

In terms of GL, you would be better to use Vertex Arrays instead of glvertex. This means only one driver call instead of 35 here. However if you dont have access to vertex arrays in your gl imlpementation, you should use a display list to wrap this up.

Chris

Challenge Trophies Won:

Offline Pixel_Outlaw

  • Pentium
  • *****
  • Posts: 1382
  • Karma: 83
    • View Profile
Hmm somewhere I seem to recall the statement that incremental construction of angles is not as precise as multiplication by a base because of truncation and such. In this case the subangle is the angle between points. Wont the incremental method produce poor results due to truncation in floating values?

Example if the angle was 33.3(repeating) each new addition of the 33.3... angle would ruin the next angle.

Thanks for answering my main question! I shall forge on late into the evening rewriting code with a savory temptress (diet coke) at my side.
« Last Edit: October 06, 2007 by Pixel_Outlaw »
Challenge Trophies Won:

Offline taj

  • Bytes hurt
  • DBF Aficionado
  • ******
  • Posts: 4810
  • Karma: 189
  • Scene there, done that.
    • View Profile
Hmm somewhere I seem to recall the statement that incremental construction of angles is not as precise as multiplication by a base because of truncation and such. In this case the subangle is the angle between points. Wont the incremental method produce poor results due to truncation in floating values?

This is _theoretically_ right, but no way will you see the effect in your example or infact in 99% of uses. Can you really see the difference between rotating 66.24567 degrees and 66.24566 degrees? In some rare cases yes but in general no. I'll write a program to test how severe the problem is or isnt for you.

Chris
Challenge Trophies Won:

Offline Jim

  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 5301
  • Karma: 402
    • View Profile
An ordinary float has 23 bits of precision, that's 1 part in 8million (2^23) or so that might get lost when you add 2 numbers of the same scale (the same power of 2).  So probably not a problem in this application.

One thing you can do is remove the multiply by radius by using glScale.
ie. add
Code: [Select]
glScalef(radius, radius, 1)
before the loop and it'll get built into the transformation matrix.

Other people will come along and say that you need even less precision in your angles.  Let's say you only wanted 1/16th of a degree accuracy, then you can pre-calculate sin() and cos() values into an array.
Code: [Select]
dim zin(360*16)
dim coz(360*16)
for i = 0 to 360*16
  zin(i) = sin(i/16)
  coz(i) = cos(i/16)
next
The reason for this is sin() and cos() are slow functions.
The your main loop becomes
Code: [Select]
         glvertex3f(coz(int(subangle*16))*radius,zin(int(subangle*16))*radius,0)
I've chosen 16 because the compiler will be able to use special tricks for multiplying and dividing by powers of 2 using shifts.
Whether this is really worthwhile in this day and age is questionable, since sin and cos can be calculated very quickly by a modern Intel CPU, but if you call sin and cos a lot it might make a difference.

Combine that with what Chris said and the code disappears to nothing :)

Jim
« Last Edit: October 06, 2007 by Jim »
Challenge Trophies Won:

Offline taj

  • Bytes hurt
  • DBF Aficionado
  • ******
  • Posts: 4810
  • Karma: 189
  • Scene there, done that.
    • View Profile
Heres some code to test precision of floats, and the error accumulation.
The program tries to add a number n times in a loop and also, simply multiplies the number by n. The difference is reported as the error.

From this code I think you can say:

1. When your loop is around 10,000 iterations the results can be significant
2. at 100,000 iterations the error *is* significant.

Any loop of a a few thousand or less iterations will show no discernable problem.

Challenge Trophies Won:

Offline Jim

  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 5301
  • Karma: 402
    • View Profile
It depends what numbers you're adding though.
Since 1/10 is not representable as a binary IEEE floating point number exactly (same way 1/3 isn't exactly representable in decimal), then you will accumulate errors with every addition.  But if the number is a sum of powers of 2, eg, 1/2+1/16 then there will not be any errors at all, until you get underflow (the number you are adding to is so large that adding the small number makes no difference).

Jim
Challenge Trophies Won:

Offline taj

  • Bytes hurt
  • DBF Aficionado
  • ******
  • Posts: 4810
  • Karma: 189
  • Scene there, done that.
    • View Profile
Jim,

agreed, I was trying to show a fairly tough case...hence the choice of something very close to 1/3rd.

Chris
Challenge Trophies Won:

Offline Jim

  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 5301
  • Karma: 402
    • View Profile
:) It's interesting to see the error doesn't necessarily get any worse.  After 29483 iterations of adding 1/3 the absolute error is less than 0.00004, whereas after 24568 the error is 1.598.  The error at 100000 iterations (23.2) is less than it was at 98230 (25.5).  I wasn't expecting that.
The absolute error gets quite large, 25 or so, but at no time is the percentage error (incremental/multiplied) worse than 1 part in 1000, about 10bits accuracy still.

Again the good news is that 1 part in 1000 is easily good enough for most 3d graphics.

I've had loads of discussions with people on floating point - it's amazing how few programmers understand the massive pitfalls they have which come as part and parcel of the massive benefits! :)

Jim
Challenge Trophies Won:

Offline rain_storm

  • Here comes the Rain
  • DBF Aficionado
  • ******
  • Posts: 3088
  • Karma: 182
  • Rain never hurt nobody
    • View Profile
    • org_100h
This is a problem for any fraction x/n where n is not a power of 2. Only if n IS a power of 2 can the fraction be represented exactly within range of floating point arithmetic.
however sin / cos cannot ever be exactly represented (the same way as pi cannot be eactly represented) not by floating point math nor by using the decimal system with pen and paper nor by any other means

Challenge Trophies Won:

Offline frea

  • C= 64
  • **
  • Posts: 61
  • Karma: 2
    • View Profile
in order to speed things up you can create a lookup table.
Calculate sin value only once, store them in an array, and use these stored values for glVertex. This should be much quicker that calculating sin over and over.
Nananan.

Offline Pixel_Outlaw

  • Pentium
  • *****
  • Posts: 1382
  • Karma: 83
    • View Profile
Thanks all. I'm still working on this library and functions. :clap:
Challenge Trophies Won: