Performance Tuning

From Director Online 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.

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