Performance Tuning

From Directorforum Collaboration Wiki

Jump to: navigation, search

There are many ways to tune performance in Director. This page will try to help you get your project run as fast as possible. Would be great to see you add to this list.

Contents

Use multiplication instead of division

Multiplication is about 2% faster than division. So try to use multiplication wherever possible.

-- Lingo performance comparison of multiplication versus division
 
on startMovie
  RunMultiply()
  RunDivide()
end
 
on RunMultiply
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- fast:
    x = i * 0.1
  end repeat
  put "Multiplication:" && the milliseconds - a & "ms"
end
 
on RunDivide
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- slow:
    x = i / 10.0
  end repeat
  put "Division:" && the milliseconds - a & "ms"
end
 
-- Result:
-- "Multiplication: 205ms"
-- "Division: 210ms"

Store and use a reference to sprites

Instead of each time use a new reference to a sprite, create it once, store it (e.g. as a property) and use this stored reference. Performance gain is significant (about 50%). In fact you should always use references. This is only a simple example.

property pSprite
 
on beginSprite me
  pSprite = sprite(me.spriteNum)
  RunSpriteReferenceAccess(me)
  RunSpriteAccess(me)
end
 
on RunSpriteReferenceAccess me
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- fast:
    x = pSprite.width
  end repeat
  put "SpriteReferenceAccess:" && the milliseconds - a & "ms"
end
 
on RunSpriteAccess me
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- slow:
    x = sprite(me.spriteNum).width
  end repeat
  put "SpriteAccess:" && the milliseconds - a & "ms"
end
 
-- Result:
-- "SpriteReferenceAccess: 201ms"
-- "SpriteAccess: 394ms"

Store and use a reference to members

Instead of each time use a new reference to a member, create it once, store it (e.g. as a property) and use this stored reference. Performance gain is about 15%. In fact you should always use references. This is only a simple example.

property pMember
 
on beginSprite me
  pMember = sprite(me.spriteNum).member
  RunMemberReferenceAccess(me)
  RunMemberAccess(me)
end
 
on RunMemberReferenceAccess me
  a = the milliseconds
  repeat with i = 1 to 100000
    -- fast:
    x = pMember.text
  end repeat
  put "MemberReferenceAccess:" && the milliseconds - a & "ms"
end
 
on RunMemberAccess me
  a = the milliseconds
  repeat with i = 1 to 100000
    -- slow:
    x = sprite(me.spriteNum).member.text
  end repeat
  put "MemberAccess:" && the milliseconds - a & "ms"
end
 
-- Result:
-- "MemberReferenceAccess: 325ms"
-- "MemberAccess: 380ms"

"if then" construct faster than "case of"

As long as your program logic allows, you should use "if then" construct instead of the "case of" construct. The performance gain is about 10%.

on startMovie
  loopIf()
  loopCase()
end
 
on loopIf
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- fast:
    if a = 0 then
      nothing
    end if
  end repeat
  put "loopIf:" && the milliseconds - a & "ms"
end
 
on loopCase
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- slow:
    case a of
      0: nothing
    end case
  end repeat
  put "loopCase:" && the milliseconds - a & "ms"
end
 
-- Results:
 -- "loopIf: 111ms"
 -- "loopCase: 125ms"

Check a list with count property

If you need to check if a list already contains items, you should use the count property instead of checking against an empty list. The count property is retrieved more than 10 times faster!

global gList
 
on startMovie
  gList = ["a"]
  checkListCount()
  checkList()
end
 
on checkListCount
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- fast:
    if gList.count = 0 then
      nothing
    end if
  end repeat
  put "checkListCount:" && the milliseconds - a & "ms"
end
 
on checkList
  a = the milliseconds
  repeat with i = 1 to 1000000
    -- slow:
    if gList = [] then
      nothing
    end if
  end repeat
  put "checkList:" && the milliseconds - a & "ms"
end
 
-- Results:
-- "checkListCount: 145ms"
-- "checkList: 1653ms"

Concatenate strings using "put after"

The old and verbose construct "put x after y" construct is magnitudes faster than using the ampersand(s) to concatenate strings.

on startMovie
  concatStringPutAfter()
  concatStringAmpersand()
end
 
on concatStringPutAfter
  a = the milliseconds
  myString = ""
  repeat with i = 1 to 100000
    -- fast:
    put "a" after myString
  end repeat
  put "concatStringPutAfter:" && the milliseconds - a & "ms"
end
 
on concatStringAmpersand
  a = the milliseconds
  repeat with i = 1 to 100000
    -- slow:
    myString = myString & "a"
  end repeat
  put "concatStringAmpersand:" && the milliseconds - a & "ms"
end
 
-- Result:
-- "concatStringPutAfter: 25ms"
-- "concatStringAmpersand: 1097ms"

Methods for member access

You can access member by it's name or by it's number, include cast number or name, or use internal full number, that includes both member number and cast number together. Adressing member by single name is slowest way, especially if many casts are attached. Full number calculated as memberNum + castNum * 65536 (except for Internal cast)

property pMemberName
property pMemberNum
property pCastName
property pCastNum
property pMemberFullNum
 
on beginSprite me
 
  pMemberName = "someMember"
  pMemberNum = 2
  pCastName = "extCast"
  pCastNum = 3
  pMemberFullNum = 196610
 
  RunMemberNameAccess(me)
  RunMemberNameWithCastNameAccess(me)
  RunMemberNumWithCastNumAccess(me)
  RunMemberFullNumAccess(me)
 
end
 
on RunMemberNameAccess me
  a = the milliseconds
  repeat with i = 1 to 100000
    -- slow:
    x = member(pMemberName)
  end repeat
  put "MemberNameAccess:" && the milliseconds - a & "ms"
end
 
on RunMemberNameWithCastNameAccess me
  a = the milliseconds
  repeat with i = 1 to 100000
    -- slow:
    x = member(pMemberName, pCastName)
  end repeat
  put "MemberNameWithCastNameAccess:" && the milliseconds - a & "ms"
end
 
on RunMemberNumWithCastNumAccess me
  a = the milliseconds
  repeat with i = 1 to 100000
    -- fast:
    x = member(pMemberNum, pCastNum)
  end repeat
  put "MemberNumWithCastNumAccess:" && the milliseconds - a & "ms"
end
 
on RunMemberFullNumAccess me
  a = the milliseconds
  repeat with i = 1 to 100000
    -- fastest:
    x = member(pMemberFullNum)
  end repeat
  put "MemberFullNumAccess:" && the milliseconds - a & "ms"
end
 
-- Result:
-- "MemberNameAccess: 512ms"
-- "MemberNameWithCastNameAccess: 436ms"
-- "MemberNumWithCastNumAccess: 53ms"
-- "MemberFullNumAccess: 40ms"

The last method is most useful, if you keep long list of data for initialization purpose, in that case you'll have simple list of numbers, each of them have fastest direct way to member.

Copying a partial source image with copyPixels

When copying a source image to a destination image where the destRect is not completely inside the destination image, Director only copies the section which will be visible inside the image, thus it is unneccesary to write any code to check this yourself, as Director's low-level machine code will do it faster and all in one step.

The following code shows an entire image being copied, an image being copied with partially overlapping rects, a partial image being copied with the sourceRect changed to accomodate this, and finally an image being copied where the destRect does not intersect the destination image at all.

global img1, img2
 
on measure fn, n
  st = _system.milliseconds
  repeat with i = 1 to n
    call(fn, script(1))
  end repeat
  return _system.milliseconds - st
end
 
on test1
  -- complete intersection, complete sourceRect
  img1.copyPixels(img2, rect(100, 100, 1100, 1100), rect(0, 0, 1000, 1000))
end
 
on test2
  -- partial intersection, complete sourceRect
  img1.copyPixels(img2, rect(-500, -500, 500, 500), rect(0, 0, 1000, 1000))
end
 
on test3
  -- complete intersection, partial sourceRect
  img1.copyPixels(img2, rect(0, 0, 500, 500), rect(500, 500, 1000, 1000))
end
 
on test4
  -- no intersection, complete sourceRect
  img1.copyPixels(img2, rect(-1000, -1000, 0, 0), rect(0, 0, 1000, 1000))
end
 
-- results:
img1 = image(2000, 2000, 32)
img2 = image(1000, 1000, 32)
 
put measure(#test1, 100)
-- 2710
 
put measure(#test2, 100)
-- 675
 
put measure(#test3, 100)
-- 678
 
put measure(#test4, 100)
-- 3

Use repeat with..to instead of repeat with..down to

Try to always avoid repeat loops where possible, because they lock user interaction while they're running. But if you need to use them, avoid the repeat with..down to version, because it's slower than the repeat with..to version. Also keep in mind, that you should store the loop count in a variable instead of retrieving the number from a property over and over again.

-- Lingo Performance Test
 
on startMovie
  a = the milliseconds
  -- fast:
  repeat with x = 1 to a
    nothing
  end repeat
  put "repeat with .. to:" && (the milliseconds - a) & "ms"
  a = the milliseconds
  -- slow:
  repeat with x = a down to 1
    nothing
  end repeat
  put "repeat with .. down to:" && (the milliseconds - a) & "ms"
end
 
-- Results:
-- "repeat with .. to: 374ms"
-- "repeat with .. down to: 397ms"

Calculating a power value

n*n is about 3 times faster than power(n,2). So try to use multiplication wherever possible. even for higher powers (taken 8 here) power() still does not do better

-- Lingo performance comparison of multiplication versus power()
on test()
  tempTime=The milliseconds
 
  repeat with n=1 to 1000000
    d=power(n,2)
  end repeat
 
  put "Time power(n,2): " & string(the milliseconds-tempTime) 
 
  tempTime=The milliseconds
 
  repeat with n=1 to 1000000
    d=n*n
  end repeat
 
  put "Time n*n: " & string(the milliseconds-tempTime)  
 
  tempTime=The milliseconds
 
  repeat with n=1 to 1000000
    d=power(n,8)
  end repeat
 
  put "Time power(n,8): " & string(the milliseconds-tempTime)   
  tempTime=The milliseconds
 
  repeat with n=1 to 1000000
    d=n*n*n*n*n*n*n*n
  end repeat
 
  put "Time n*n*n*n*n*n*n*n: " & string(the milliseconds-tempTime)   
  --result: 
  -- "Time power(n,2): 667"
  -- "Time n*n: 239"
  -- "Time power(n,8): 933"
  -- "Time n*n*n*n*n*n*n*n: 351"
 
end


Basic list deletion

When a deleteAt() command is used the list moves all the higher index values one place down. This is very basic but can help speed things up if you were not aware.


on Test()
 
  List1=[]
  List2=[]
  repeat with n=1 to 30000
    list1.add(n)
    list2.add(n)
  end repeat
 
 
  tempTime=The milliseconds
 
  repeat with n=1 to 30000
    list1.deleteAt(1)
  end repeat
 
  put "removing an entire list from 1 :" the milliseconds-tempTime
 
  tempTime=the milliseconds
 
  repeat with n=1 to 30000
    list2.deleteAt(list2.count)
  end repeat
 
  put "removing an entire list from LastValue :" the milliseconds-tempTime
 
 
  --result:
  -- "removing an entire list from 1 :" 761
  -- "removing an entire list from LastValue :" 12
 
end


Object comparing

Comparing two objects is faster than comparing ID properties of those objects (.ID = integer). [Guess the values returned with put Object (like: <offspring "testObject" 2 2533360>) are not compared but their internal values are. ]

on test()
 
  compareObject1=new(script "testObject", random(1000))
  compareObject2=new(script "testObject", random(1000))
 
  tempTime=The Milliseconds
 
  repeat with n=1 to 1000000
 
    if compareObject1=CompareObject2 then
    else
    end if
 
  end repeat
 
  put "object Compare: " &string(the milliseconds-tempTime)
 
  tempTime=The Milliseconds
 
  repeat with n=1 to 1000000
 
    if compareObject1.ID=CompareObject2.ID then
    else
    end if
 
  end repeat
 
  put "Object.ID Compare: " &string(the milliseconds-tempTime)
 
  --result:
  -- "object Compare: 262"
  -- "Object.ID Compare: 400"
 
end
 
 
--in parent Script (with name "testObject") 
 
--property ID
--on new me, iID
 
--  ID=iID
 
--  return me
 
--end


Finding a Object in list

Sometimes you want to find a unique object/instance in a list. And this works as a great speed boost! (100 times faster) list.getOne(aInstance) Alternatives are List.DeleteOne() and List.getPos() <Can also be used in other find in list cases. and it is documented in director help> Need to have a parent script "Dummie"

on test()
 
  --find a (unique) object in a list
 
  aLongListofObjects=[]
 
  repeat with n=1 to 1000001
    aInstance=new(script "dummie")
    aLongListOfObjects.add(aInstance)
  end repeat
 
  InstanceToFind=aLongListOfObjects[1000000]  
  aLOcount=aLongListofObjects.count
  found=0
 
  -------Normal
 
  TheMS=the milliseconds
 
  repeat with n=1 to aLOcount
 
    if aLongListOfObjects[n]=InstanceToFind then
      found=n
      exit repeat
    end if
  end repeat
 
  Finish=the milliseconds
 
  put found
 
  put "normal: " & string(Finish-theMS)  &" ms"
 
 
 
  --------getOne
 
  TheMS=the milliseconds
 
  found=aLongListOfObjects.getOne(InstanceToFind)
 
  Finish=the milliseconds
 
  put found
  put "getOne: " & string(Finish-theMS)  &" ms"
 
 
end
 
 
-------RESULT:
--1e6 objects in list
--index [1] = fast (0ms)
--index 50%;  => normal:204 ms, getOne 3ms  
--index last  => normal:418 me, getOne 5ms


calling a handler on a list of objects/instances (or scripts)

In games it can occur a handler is called to every instance. Like Unit.UpdateLocation(dTime). I used a repeat to do this... but for these things there is a better way : Call(#handlername, [Instances], *arguments/parameters) The added value will strongly depend on your application and what consumes the most CPU power in your application.


on speedTest()
 
  InstanceList=[]
  repeat with n=1 to 1000000
    InstanceList.add(new(script "boris"))
  end repeat
 
  -------------------------------------------
  theMS=the milliseconds
 
  InstanceListCount=InstanceList.count
  repeat with n=1 to InstanceListCount
    InstanceList[n].test()
  end repeat
 
  finish=the milliseconds
 
  put "repeat: " & string(Finish-theMS)
 
  -------------------------------------------
  theMS=the milliseconds
 
  Call(#test, InstanceList)
 
  finish=the milliseconds
 
  put "callList: " & string(Finish-theMS)
 
 
  -------------------------------------------
  theMS=the milliseconds
 
  InstanceListCount=InstanceList.count
  repeat with n=1 to InstanceListCount
    InstanceList[n].test(3.15, 4, vector(1.0,2.3,4.1))
  end repeat
 
  finish=the milliseconds
 
  put "repeat with arg: " & string(Finish-theMS)
 
  -------------------------------------------
  theMS=the milliseconds
 
  Call(#test, InstanceList, 3.15, 4, vector(1.0,2.3,4.1))
 
  finish=the milliseconds
 
  put "callList with arg: " & string(Finish-theMS)
  -------------------------------------------
 
  --  RESULT:
  -- "repeat: 626"
  -- "callList: 306"
  -- "repeat with arg: 1325"
  -- "callList with arg: 361"
 
 
 
end

Dot syntax interpreter order

When using parent script instances it can occur one of the properties is a list. Sometimes director will interpret a line of code the right way only after failing the wrong way (please correct this if its wrong, also if the order of interpreting {where do i find this?} makes this claim a logical consequence). This will have consequences for aObj.aList[n], for aObj.bObj.cList[n] but also aObj.aList.count. Useing the syntax like this will work but it will first execute like aObj.getProp(#aList, n). Using (aObj.aList)[n] the getProp() methode will not be called. (it is assumed the same thing will hapen for .count)


on test()
 
  testObj=new(script "testObj")
 
 
  theMS=the milliseconds
 
  repeat with n=1 to 10000000
 
    var1=testObj.aTestList[2]
 
  end repeat
 
  finish=the milliseconds
 
  put "-------------"
  put "without () : " string(finish-theMS) & " ms"
  put "var1: " & string(var1)
  put "-------------"
 
 
  theMS=the milliseconds
 
  repeat with n=1 to 10000000
 
    var1=(testObj.aTestList)[2]
 
  end repeat
 
  finish=the milliseconds
 
  put "-------------"
  put "with () : " string(finish-theMS) & " ms"
  put "var1: " & string(var1)
  put "-------------"
 
end
 
 
--RESULTS:
-- "---[2]----"
-- "without () : " "4533 ms"
-- "var1: 2"
-- "-------------"
-- "-------------"
-- "with () : " "4016 ms"
-- "var1: 2"
-- "-------------"
 
-- "--Count---"
-- "without () : " "4234 ms"
-- "var1: 4"
-- "-------------"
-- "-------------"
-- "with () : " "3764 ms"
-- "var1: 4"
-- "-------------"
 
 
 
--CONCLUSION:
--it is faster to include the ()
Personal tools