Performance Tuning
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
- 1 Use multiplication instead of division
- 2 Store and use a reference to sprites
- 3 Store and use a reference to members
- 4 "if then" construct faster than "case of"
- 5 Check a list with count property
- 6 Concatenate strings using "put after"
- 7 Methods for member access
- 8 Copying a partial source image with copyPixels
- 9 Use repeat with..to instead of repeat with..down to
- 10 Calculating a power value
- 11 Basic list deletion
- 12 Object comparing
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) --311 ms (zelfde locatie als rect) tempTime=The milliseconds repeat with n=1 to 1000000 d=n*n end repeat put "Time n*n: " & string(the milliseconds-tempTime) --311 ms (zelfde locatie als rect) tempTime=The milliseconds repeat with n=1 to 1000000 d=power(n,8) end repeat put "Time power(n,8): " & string(the milliseconds-tempTime) --311 ms (zelfde locatie als rect) 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) --311 ms (zelfde locatie als rect) --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