News:

Building a 3D Ray Tracer  By stevmjon

Main Menu

How big should we make Functions?

Started by stevmjon, September 06, 2023, 11:07:54 PM

Previous topic - Next topic

stevmjon

i generally try to make functions as big as i can, because i remember kev mentioning this ages ago, so as to keep the program running faster.
lots of smaller functions can slow down the frames per second.

maybe we can change this to make smaller easier to manage functions because computers these days run faster.

what do you guys think?
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

kevin

#1
   Generally (in programming languages)  every time you call a function you're allocating space on the stack (for the stuff used within the function)... Then when you leave the function the space is released..

  So there's balance between the function size and frequency of calls to that function, as just calling costs you, let alone what the function does inside it.  
 
 In computer science you'll hear coders talk about determinism and single responsibilities among other things for functions.   Generally such conversations occur where performance isn't the primary concern;  readability is the objective.    There's always these trades off where we have to manage the convenience of a thing verses the suitability of that thing.  


 Imagine you write a function to return if an integer is negative or not..  


PlayBASIC Code: [Select]
Function IsNegative(Value)
Result = Value<0
EndFunction Result



 
  It's easy to imagine that such a helper function would make the code above it nice and easy to read.  Which is a good thing..  

  So if our program only uses such  helper functions a bit;  then its not worth inlining the expressions;  which would just make those higher levels of code harder to read/modify in the future..  

  But if our program is running through a large array of data and calling IsNegative for each item in.  Then there's a conflict of interest between readability and performance.  



  If we take some empty functions and call them 500K times we find there's a sizable amount of overhead there...  

PlayBASIC Code: [Select]
   // -------------------------------------------------------
// FUNCTION VERSION OF TEST
// -------------------------------------------------------



tests=100000

do
cls

print fps()

frames++


t=timer()
for lp=0 to tests
Test0()
Test0()
Test0()
Test0()
Test0()
next
t0#+=timer()-t
print "Test0="+str$(t0#/frames)



t=timer()
for lp=0 to tests
Test1(a)
Test1(a)
Test1(a)
Test1(a)
Test1(a)
next
t1#+=timer()-t
print "Test1="+str$(t1#/frames)


t=timer()
for lp=0 to tests
Test2(a,b)
Test2(a,b)
Test2(a,b)
Test2(a,b)
Test2(a,b)
next
t2#+=timer()-t
print "Test2="+str$(t2#/frames)


t=timer()
for lp=0 to tests
Test3(a,b,c)
Test3(a,b,c)
Test3(a,b,c)
Test3(a,b,c)
Test3(a,b,c)
next
t3#+=timer()-t
print "Test3="+str$(t3#/frames)


t=timer()
for lp=0 to tests
Test4(a,b,c,d)
Test4(a,b,c,d)
Test4(a,b,c,d)
Test4(a,b,c,d)
Test4(a,b,c,d)
next
t4#+=timer()-t
print "Test4="+str$(t4#/frames)


t=timer()
for lp=0 to tests
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
next
t5#+=timer()-t
print "Test5="+str$(t5#/frames)


sync
loop spacekey()



function Test0()
EndFunction

function Test1(value)
EndFunction

function Test2(value1,value2)
EndFunction

function Test3(value1,value2,value3)
EndFunction

function Test4(value1,value2,value3,value4)
EndFunction

function Test5(value1,value2,value3,value4,value5)
EndFunction






  And the PSUB version

PlayBASIC Code: [Select]
   // -------------------------------------------------------
// PSUB VERSION OF TEST
// -------------------------------------------------------

tests=100000

do
cls

print fps()

frames++


t=timer()
for lp=0 to tests
Test0()
Test0()
Test0()
Test0()
Test0()
next
t0#+=timer()-t
print "Test0="+str$(t0#/frames)



t=timer()
for lp=0 to tests
Test1(a)
Test1(a)
Test1(a)
Test1(a)
Test1(a)
next
t1#+=timer()-t
print "Test1="+str$(t1#/frames)


t=timer()
for lp=0 to tests
Test2(a,b)
Test2(a,b)
Test2(a,b)
Test2(a,b)
Test2(a,b)
next
t2#+=timer()-t
print "Test2="+str$(t2#/frames)


t=timer()
for lp=0 to tests
Test3(a,b,c)
Test3(a,b,c)
Test3(a,b,c)
Test3(a,b,c)
Test3(a,b,c)
next
t3#+=timer()-t
print "Test3="+str$(t3#/frames)


t=timer()
for lp=0 to tests
Test4(a,b,c,d)
Test4(a,b,c,d)
Test4(a,b,c,d)
Test4(a,b,c,d)
Test4(a,b,c,d)
next
t4#+=timer()-t
print "Test4="+str$(t4#/frames)


t=timer()
for lp=0 to tests
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
Test5(a,b,c,d,e)
next
t5#+=timer()-t
print "Test5="+str$(t5#/frames)


sync
loop spacekey()



psub Test0()
Endpsub

psub Test1(value)
Endpsub

psub Test2(value1,value2)
Endpsub

psub Test3(value1,value2,value3)
Endpsub

psub Test4(value1,value2,value3,value4)
Endpsub

psub Test5(value1,value2,value3,value4,value5)
Endpsub







     Does this mean we shouldn't use functions and retreat to psubs or even gosub/return logic for sub routines ?? 


     No....


     It mean if performance is a paramount and we've exhausted ourselves algorithmically (there's often many more solutions than our initial way of doing it)  then we might start looking to pre-compute or even unroll inner most bits of logic in and effort to squeeze out the last bits of performance.   

    Generally only a tiny portion of our application will be consuming the most resources in execution so we don't have to throw out the dishes with the bath water and give up all luxuries just to win a bit of performance back.. Most of the biggest gains come through choosing a more appropriate algorithm(s) to help remove redundancies