How-To: Improving a code speed?

Hi
I am a novice python coder and I would like to hear your opinions and suggestions about my function.


# Preset data list
afp = [0.0, 0.0, 45.0, -0.5, 65.0, -0.1, 70.0, -0.6, 90.0, 0.1, 105.0, 0.9, 110.0, 0.12, 135.0, 0.6, 180.0, 0.0]
 
def i_c(val,d, ia = 0.0):   # linear interpolation for the Cl values.  Max 9 sets/pairs.
    vali = val + ia # add a incident of attack to the val value if needed
    if vali <= d[2]: # a normal opeation range
        return d[1] + ((vali - d[0])*(d[3]-d[1])) / (d[2]-d[0])
    if (vali > d[2]) is True and (vali <= d[4]) is True:
        return d[3] + ((vali - d[2])*(d[5]-d[3])) / (d[4]-d[2])
    if (vali > d[4]) is True and (vali <= d[6]) is True:
        return d[5] + ((vali - d[4])*(d[7]-d[5])) / (d[6]-d[4])
    if (vali > d[6]) is True and (vali <= d[8]) is True:
        return d[7] + ((vali - d[6])*(d[9]-d[7])) / (d[8]-d[6])
    if (vali > d[8]) is True and (vali <= d[10]) is True:
        return d[9] + ((vali - d[8])*(d[11]-d[9])) / (d[10]-d[8])
    if (vali > d[10]) is True and (vali <= d[12]) is True:
        return d[11] + ((vali - d[10])*(d[13]-d[11])) / (d[12]-d[10])
    if (vali > d[12]) is True and (vali <= d[14]) is True:
        return d[13] + ((vali - d[12])*(d[15]-d[13])) / (d[14]-d[12])
    if (vali > d[14]) is True and (vali <= d[16]) is True:
        return d[15] + ((vali - d[14])*(d[17]-d[15])) / (d[16]-d[14])
    if (vali > d[16]) is True and (vali <= d[18]) is True:
        return d[17] + ((vali - d[16])*(d[19]-d[17])) / (d[18]-d[16])
 
aoa = -1.9 # angle of attack in degrees
aoa = aoa + 90.0
print i_c(aoa, afp)

The function is interpolating the coefficient of lift (cl) value, based to the given angle of attack (aoa) degree value.

The afp-list contains a nine key figure pairs (aoa, cl).

The function is searching through the afp-list aoa values with ‘if’ to find a right aoa key figure and then interpolate the cl-value.

How I can improve the code speed? The nominal aoa-range is 0-10 degrees.

Thanks

While I don’t necessarily think this will speed it up, it seems to me you’re doing basically the same calculation in that large number of ‘if’ statements, and you could probably just use a loop. It will at least make your code more compact. The beauty of it is you should no longer be limited to a certain number of pairs :smiley:

I’m pretty new to Python myself, so you may want to test this a bit and compare it with your old function:


# Preset data list
afp = [0.0, 0.0, 45.0, -0.5, 65.0, -0.1, 70.0, -0.6, 90.0, 0.1, 105.0, 0.9, 110.0, 0.12, 135.0, 0.6, 180.0, 0.0]

def i_c(val,d, ia = 0.0):   # linear interpolation for the Cl values.
    vali = val + ia # add a incident of attack to the val value if needed
    
    if vali <= d[2]: # a normal operation range
        return d[1] + ((vali - d[0])*(d[3] - d[1])) / (d[2] - d[0])

    for i in range(2, len(d), 2):
        if (vali > d[i]) and (vali <= d[i+2]):
            return d[i+1] + ((vali - d[i]) * (d[i+3] - d[i+1])) / (d[i+2] - d[i])
 
aoa = -1.9 # angle of attack in degrees
aoa = aoa + 90.0
print i_c(aoa, afp)

Perhaps you could store things like d[i], d[i+1] etc as local variables to avoid looking them up more than once? Though I don’t know how much you would really gain by that since I don’t know what the performance difference in Python is between local variables and array lookups :o

EDIT: after trying my version vs yours using timeit, it appears your function is actually faster (~1 second faster over 1 million iterations on my machine). The tradeoff being mine will scale to any evenly sized array of values, where yours would need more ifs

Hi Superx10

Thank you for your help.

I also made a speed tests in IDLE & BGE and I get about same result. I’m surprised. I was SURE, the multiple IF’s will make my code slow.

I made a BGE speed test file, which is also testing a IPO with very interesting result
http://blenderartists.org/forum/showthread.php?t=165959

Really the thing I see is that mine is still going to perform lots of unnecessary IFs, and it also is probably doing some stuff with range(2, len(d), 2) each time it loops. On top of that it has to do a fair amount of simple math just to be able to do the array lookups.

Yours will still slow down as you add more IFs, but mine has the IFs as well as some other things. Nothing huge, but it’s definitely slower when you run it a million times.

I’m having a bit of trouble thinking right now, but maybe later I’ll see if I can figure out how to avoid some IFs and other things :smiley:

Hi,

You could try precalculating all the parts which are not going to change.

So, you could try the following code (untested, may have some syntax errors):


# Preset data list
afp = [0.0, 0.0, 45.0, -0.5, 65.0, -0.1, 70.0, -0.6, 90.0, 0.1, 105.0, 0.9, 110.0, 0.12, 135.0, 0.6, 180.0, 0.0]

# basic algorithm
# if vali <= d[n+2]:
#    i_c(n) = d[n+1] + ((vali - d[n])*(d[n+3]-d[n+1])) / (d[n+2]-d[n])

# Precalculated multipliers
# TODO: for more speed, calculate these out by hand first (like for afp list)
# NOTE: if you get any errors, double check the range here...
dmult = [ ((afp[n+3]-afp[n+1]) / (afp[n+2]-afp[n])) for n in xrange(len(n)-3) ]

# linear interpolation for the Cl values.  Max 9 sets/pairs.
def i_c(val, ia = 0.0): 
    # make py look for these vars from the globals
    # NOTE: this may/may not reduce copying time needed / memory used
    global afp, dmult
    
    # add a incident of attack to the val value if needed
    vali = val + ia 
    
    # a normal opeation range
    # NOTE: less stuff multiplied here, and also note that fewer things need to be checked for
    # since you've caught the lower bound already in the previous if's
    if vali <= afp[2]: 
        return afp[1] + (vali - afp[0])*dmult[0]
    elif vali <= afp[4]:
        return afp[3] + (vali - afp[2])*dmult[1]
    elif vali <= afp[6]:
        return afp[5] + (vali - afp[4])*dmult[2]
    elif vali <= afp[8]:
        return afp[7] + (vali - afp[6])*dmult[3]
    elif vali <= afp[10]:
        return afp[9] + (vali - afp[8])*dmult[4]
    elif vali <= afp[12]:
        return afp[11] + (vali - afp[10])*dmult[5]
    elif vali <= afp[14]:
        return afp[13] + (vali - afp[12])*dmult[6]
    elif vali <= afp[16]:
        return afp[15] + (vali - afp[14])*dmult[7]
    elif vali <= afp[18]:
        return afp[17] + (vali - afp[16])*dmult[8]
        

aoa = -1.9 # angle of attack in degrees
aoa = aoa + 90.0
print i_c(aoa)

You can do conditional statements without ‘if.’ I’m not sure how much faster it is in Python, and it looks a bit messy, but things like:


a = b + (c < d) * (e - b)

instead of


if c >= d:
   a = b
else:
   a = e

works wonders in some languages.

It uses the comparison (a < b, a == b etc.) as a bool argument, 1 for True, 0 for False, great for ‘then use this not that.’

Comparisons still take some time but there’s no jump, which should easily make up for a bit of extra discarded math (b + True * (e - b), which equals (b - b) + e or just e).

Also assumed cases skip the ‘else,’ making 0 or 1 jumps instead of always 1:


a = b # Assumed
if c &lt; d:
   a = e # Fast simple change

This only works well if the cases are fast (simple math or assignment) and reasonably likely compared to each other, or the slow one (eg. function call) is in the ‘if’ and less likely.

If your ‘afp’ data was evenly spaced (eg. 15 degrees), or is meant to be, you could even do:


idx = int (vali / 15 + 0.5)  # + 0.5 rounds off
return afp[idx+idx+1] + (vali - afp[idx+idx]) * dmult[idx]

2 lines, no waiting.

Separating afp could avoid the confusion with idx*2+1:


idx = int (vali / 15 + 0.5)  # + 0.5 rounds off
return afpHi[idx] + (vali - afpLo[idx]) * dmult[idx]

Then you could also use a class or other combined data type.

Dave Heinemann

Hi

Thank you for everybody for the comments & ideas.

I was making a very simple and streamlined linear interpolation tests without any 'if’s and the IPO method was still ~50% faster.

Now back to the python book:confused: