Performance Tuning
From Directorforum Collaboration Wiki
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.
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 ()
