Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

[Tips] Optimization 101
Started by Danny Dec 02 2011 06:45 PM

* * * * * 8 votes
87 replies to this topic
[TOPIC CONTROLS]
Page 1 of 4 1 2 3 »
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

Danny

[GLOBAL: userInfoPane.html]
Danny
  • Corona Geek

  • 2,597 posts
  • Corona Staff

http://developer.anscamobile.com/content/performance-and-optimization

Hey guys, just want to share some optimization techniques here with you. I will update this list when I can or add any techniques you guys may suggest.

1) Localization : Localizing Lua functions provides a decent > massive speed difference. How do you do this ? Like so :

The slower way (Bad)

local myRand = math.random(1, 10)

The faster way (Good)
local mRand = math.random

local myRand = mRand(1, 10)

Other examples of this

local mRandSeed = math.randomseed

--Call it
mRandSeed(os.time())

local sFormat = string.format

--Call it
sFormat("%02d", myVar)

local tRemove = table.remove

--Call it
tRemove(myTable)

2) Avoid moving objects manually. Moving objects by incrementing it's x,y values each frame is slower than using translate.

Example :

The bad way

obj.x = obj.x + 1
obj.y = obj.y + 1

The good way

obj:translate(1, 1)

You can also use translate even if you only need to just increase the x or y and not both as shown below :

--Only increment the x value
obj:translate(1, 0)

--Only increment the y value
obj:translate(0, 1)

3) Avoid heavy string comparison. If you are doing a lot of string comparison checks like below :

local myString = "none"

if myString == "hi" or myString == "do" or myString == "lol" then

It is faster to use a table of enums. That way you can still know the values but you are comparing numbers which is much faster than comparing strings.

local myString = 1

local enumTable = {
    ["hi"] = 1,
    ["do"] = 2,
    ["lol"] = 3,
}

if myString == enumTable["hi"] or myString == enumTable["do"] or myString == enumTable["lol"] then
4) For loops. Certain types of for loop execute faster than others.

Fastest :

for i = 1, #myTable do
--
end

Second fastest

local fpairs = ipairs

for i, v in fpairs(myTable) do
end

The difference between the two is roughly 120ms.

5) Make use of tables to cut down on local variable use and organise your code.

Bad practice for a group of data.


local mySheet1 = sprite.newSpriteSheetFromData("mySheet1.png", require("mySheet1").getSpriteSheetData())

local mySheet2 = sprite.newSpriteSheetFromData("mySheet2.png", require("mySheet2").getSpriteSheetData())

local mySheet3 = sprite.newSpriteSheetFromData("mySheet3.png", require("mySheet3").getSpriteSheetData())

Better practice for group of data.

local mySheets = {
    ["Sheet1"] = sprite.newSpriteSheetFromData("mySheet1.png", require("mySheet1").getSpriteSheetData())
    ["Sheet2"] = sprite.newSpriteSheetFromData("mySheet2.png", require("mySheet2").getSpriteSheetData())
    ["Sheet3"] = sprite.newSpriteSheetFromData("mySheet3.png", require("mySheet3").getSpriteSheetData())
}

To give an advantage of the benefits of this, despite the obvious ones of memory savings and organized blocks of code, take this scenario.

Attempting to dispose of the 3 sprite sheets and nil the variables using method 1 (the bad practice)

mySheet1:dispose()
mySheet2:dispose()
mySheet3:dispose()

mySheet1 = nil
mySheet2 = nil
mySheet3 = nil

Now disposing of the sprite sheets and nil the table. (the better practice)

local tRemove = table.remove

for i, v in pairs(mySheets)  do
    mySheets[i]:dispose()
    mySheets[i] = nil
end

tRemove(mySheets)

See how much easier that is to manage and remove? Thats one of the other countless benefits of using tables.

Note: Overuse of tables will consume more lua memory. So use appropriately.

6) Functions : Use less of them.

Say for instance you have 4 buttons on screen that each have a tap event. You don't need four functions (one for each), just the one will suffice.


--Table to store the buttons
local myButtons = {}

myButtons["Pause"] = display.newImage("pause.png")
myButtons["Pause"].myId = "Pause"  --Set the buttons id

myButtons["Shoot"] = display.newImage("shoot.png")
myButtons["Shoot"].myId = "Shoot" --Set the buttons id

myButtons["Move"] = display.newImage("move.png")
myButtons["Move"].myId = "Move" --Set the buttons id

myButtons["Retry"] = display.newImage("retry.png")
myButtons["Retry"].myId = "Retry" --Set the buttons id

--Function to handle our buttons
local function handleButtons(event)
     local target = event.target

     --Handle action for each different button
     if target.myId == "Pause" then
         --Pause
     elseif target.myId == "Shoot" then
         --Shoot
     elseif target.myId == "Move" then
         --Move
     elseif target.myId == "Retry" then
         ---Retry
     end
      
     return true
end

--Add event listeners for all the buttons
for i = 1, #myButtons do
    myButtons[i]:addEventListener("tap", handleButtons)
end

See one function that handles all your buttons. This saves using 4 separate functions for something that can be achieved in one :)

7) Updating Objects : Only do it when you have to.

Say for instance you are creating a space shooter and have a text object on screen to display your current score. You obviously want to update it to reflect the current score, but you don't need to do this every frame.

The bad way


local function updateScore(event)
    score = score + 1
    score.text = "score " .. score
end

Runtime:addEventListener("enterFrame", updateScore)

The good way:

--In your collision handler (when enemy gets hit with a bullet you fired
local function onCollision(event)
    --Where Bullet collides with enemy
    score = score + 1
    score.text = "score" .. score
end

That way your only updating an object when you need to, rather than wasting cpu cycles updating something when it doesn't need updating.

8) Creating objects : Things to always remember.

This is covered in greater depth here: http://blog.anscamobile.com/2011/09/how-to-spawn-objects-%E2%80%94-the-right-way/

When you have a function to create an object (or series of objects) you must return the object at the end of the function otherwise you have no way to clear the object from memory. This is also good programming practice.

Bad example:

local function spawnAGuy(amount)
    local guy = display.newImage("guy.png")
    guy.x = 100
    guy.y = 100
end

--Create the guy (note in this case your local reference is only creating a reference the the function not the created "guy" object)
local spawnedGuy = spawnAGuy

--Remove it
you cant

Better example:

local function spawnAGuy(group)
    local guy = display.newImage("guy.png")
    guy.x = 100
    guy.y = 100
    
    --Insert the guy into the specified group if it exists (then you can easily clear it from display
    if group then
        group:insert(guy)
    end
    
    return guy
end

--Create the guy
local spawnedGuy = spawnAGuy(localGroup)

--Remove it
display.remove(spawnedGuy)
spawnedGuy = nil

Best example (use parameters)

local function spawnAGuy(params)
    local guy = display.newImage("guy.png")
    guy.x = params.x or 100
    guy.y = params.y or 100
    
    --Insert the guy into the specified group if it exists (then you can easily clear it from display
    if params.group then
       prams.group:insert(guy)
    end
    
    return guy
end

--Create the guy
local spawnedGuy = spawnAGuy({x = 20, y = 130, group = localGroup})

--Remove it
display.remove(spawnedGuy)
spawnedGuy = nil

Using paramater passing is a hugely powerful feature, you can even create a function in the spawning function to destroy/remove the guy.

local function spawnAGuy(params)
    local guy = display.newImage("guy.png")
    guy.x = params.x or 100
    guy.y = params.y or 100
    
    --Insert the guy into the specified group if it exists (then you can easily clear it from display
    if params.group then
       prams.group:insert(guy)
    end
    
    --Create a function to remove the guy
    function guy:destroy()
        display.remove(self)
        self = nil
    end
    
    return guy
end

--Create the guy
local spawnedGuy = spawnAGuy({x = 20, y = 130, group = localGroup})

--Remove it
spawnedGuy:destroy()

--------------

More coming soon!, enjoy!
uid: 84637 topic_id: 18550 reply_id: 318550

  • carlospalma_sistemas likes this

[TOPIC: post.html]
#2

PixelEnvision

[GLOBAL: userInfoPane.html]
PixelEnvision
  • Contributor

  • 305 posts
  • Corona SDK

Nice post, thank you!

Specially #2, I wasn't aware of that till now...
uid: 10478 topic_id: 18550 reply_id: 71250


[TOPIC: post.html]
#3

darkconsoles

[GLOBAL: userInfoPane.html]
darkconsoles
  • Contributor

  • 795 posts
  • Guests

very cool examples, thanks
uid: 16142 topic_id: 18550 reply_id: 71266


[TOPIC: post.html]
#4

Danny

[GLOBAL: userInfoPane.html]
Danny
  • Corona Geek

  • 2,597 posts
  • Corona Staff

Just added #6, functions. Enjoy :)
uid: 84637 topic_id: 18550 reply_id: 71269


[TOPIC: post.html]
#5

naveen_pcs

[GLOBAL: userInfoPane.html]
naveen_pcs
  • Contributor

  • 266 posts
  • Corona SDK

For #2 would it still be better to use translate if your core game loop is inside an enterFrame listener (I currently increment x and y values within enterFrame).

Would it actually be a noticeable performance difference?
uid: 51654 topic_id: 18550 reply_id: 71271


[TOPIC: post.html]
#6

Danny

[GLOBAL: userInfoPane.html]
Danny
  • Corona Geek

  • 2,597 posts
  • Corona Staff

Yeah it would be better to use translate and it would be faster yeah.
uid: 84637 topic_id: 18550 reply_id: 71272


[TOPIC: post.html]
#7

don

[GLOBAL: userInfoPane.html]
don
  • Contributor

  • 638 posts
  • Corona SDK

I've done tests. #2 works even better by caching the access of x and y. It's the accessing that's expensive not the modification.

My test was to use the uma horse sprite. Display 225 of them and have them loop the animation.
Looping over all the sprites ever frame with this code produced 40-45fps*
d.x = 0 -- just this access will slow things down significantly
d.y = 0

This however produced 56-60fps*
d:translate(0, 0)
In other words, when you construct your display objects:

local d = display.newGroup()
d.cx = d.x
d.cy = d.y

-- .. somewhere modify cx, cy
local dx = d.cx + 1
local dy = d.cy + 1

d:translate(dx - d.cx, dx - d.cy)
d.cx = dx
d.cy = dy

Is much faster for large numbers of display objects over x, y, rotation, xScale, yScale. The alpha and isVisible properties however are not affected.

This has been confirmed by yanuar and I. But don't take my word for it, test it yourself.

"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil" -Donald Knuth

* numbers reported testingon 4th gen ipod touch, your will be different if you use a different sprite or device due to fillrate or other device dependent characteristics
uid: 27183 topic_id: 18550 reply_id: 71273


[TOPIC: post.html]
#8

Puzzle Runner

[GLOBAL: userInfoPane.html]
Puzzle Runner
  • Contributor

  • 394 posts
  • Corona SDK

I do agree with Don. I avoid doing stuff I know is horrible, but beyond that I will only start optimizing if my iPhone 3GS has significant performance issues. (iPhone 3GS is what I consider the "bare minimum" right now).

@Danny I'd be interested to see the performance comparison between using the transition functions versus other possible solutions. (example, trying to transition with timers instead) Also, the information you provided above should be added to the existing optimization document in the docs section so that it remains easily accessible.
uid: 36054 topic_id: 18550 reply_id: 71277


[TOPIC: post.html]
#9

jose2

[GLOBAL: userInfoPane.html]
jose2
  • Contributor

  • 108 posts
  • Corona SDK

This post need to be sticky !!

EDIT: Ok, sticked !! XD
uid: 55808 topic_id: 18550 reply_id: 71276


[TOPIC: post.html]
#10

jose2

[GLOBAL: userInfoPane.html]
jose2
  • Contributor

  • 108 posts
  • Corona SDK

+1 @blasterv , i used several times transition.to in enterframe and i need to get better perfomance.
uid: 55808 topic_id: 18550 reply_id: 71278


[TOPIC: post.html]
#11

Danny

[GLOBAL: userInfoPane.html]
Danny
  • Corona Geek

  • 2,597 posts
  • Corona Staff

@jose2 : yeah i made it a sticky for convenience ;)

@blasterv, sure. Plenty of other things will be covered too. Once i feel the list is extensive enough I will make a blog post about it and add it to the official api optimization listing.
uid: 84637 topic_id: 18550 reply_id: 71279


[TOPIC: post.html]
#12

Puzzle Runner

[GLOBAL: userInfoPane.html]
Puzzle Runner
  • Contributor

  • 394 posts
  • Corona SDK

@jose2 I try to keep enterframe fairly empty. Typically I use timers and transition API for Artifical Intelligence (when enemies must take/not take action based on actions of the player)
uid: 36054 topic_id: 18550 reply_id: 71280


[TOPIC: post.html]
#13

jose2

[GLOBAL: userInfoPane.html]
jose2
  • Contributor

  • 108 posts
  • Corona SDK

@danny, about the 4th. Is more faster user the second or the first ???. Is not clearly explained.

I read somewhere (a blog about lua optimization) about the for..do perfomance vs ipairs and tolds is more faster use for..do if you know the table length, is true ?

uid: 55808 topic_id: 18550 reply_id: 71282


[TOPIC: post.html]
#14

Naomi

[GLOBAL: userInfoPane.html]
Naomi
  • Corona Geek

  • 2,303 posts
  • Corona SDK

This is awesome! Thank you, Danny!

Please keep more tips coming!

Naomi
uid: 67217 topic_id: 18550 reply_id: 71284


[TOPIC: post.html]
#15

Danny

[GLOBAL: userInfoPane.html]
Danny
  • Corona Geek

  • 2,597 posts
  • Corona Staff

@jose2: I stated above the code snippets. The first one is the fastest :)
uid: 84637 topic_id: 18550 reply_id: 71287


[TOPIC: post.html]
#16

jose2

[GLOBAL: userInfoPane.html]
jose2
  • Contributor

  • 108 posts
  • Corona SDK

@blasterv yes but i need to use enterframe loop to know some positions of my sprites to add new graphics.

For example, a sprite who needs a "glasses" sprite i do this:

for i = eyeeffectsGroup.numChildren,1,-1 do
		
	if (eyeeffectsGroup[i]~= nil) then
		local xo = eyeeffectsGroup[i].padre.x
		local yo = eyeeffectsGroup[i].padre.y	
		local degrees = eyeeffectsGroup[i].padre.rotation
		local radians = ((degrees-90)*math.pi)/180
		eyeeffectsGroup[i].x = xo+15*math.cos(radians)
		eyeeffectsGroup[i].y = yo+15*math.sin(radians)
		eyeeffectsGroup[i].rotation = eyeeffectsGroup[i].padre.rotation

	end
end	
			

I put the glasses sprite in the eyeeffects display group and call the previous "padre" (parent) graphic to know the exactly x, y from parent and made the rotation. It's ok?
uid: 55808 topic_id: 18550 reply_id: 71283


[TOPIC: post.html]
#17

jose2

[GLOBAL: userInfoPane.html]
jose2
  • Contributor

  • 108 posts
  • Corona SDK

Thanks @danny !!

So, for example in the code in #13 what's the best way to optimize this ??

This code execute on enterframe
uid: 55808 topic_id: 18550 reply_id: 71289


[TOPIC: post.html]
#18

Naomi

[GLOBAL: userInfoPane.html]
Naomi
  • Corona Geek

  • 2,303 posts
  • Corona SDK

[deleting the double post]
uid: 67217 topic_id: 18550 reply_id: 71285


[TOPIC: post.html]
#19

don

[GLOBAL: userInfoPane.html]
don
  • Contributor

  • 638 posts
  • Corona SDK

@Danny

What do you think about creating a series of tests so we can measure this stuff and have everyone report back? I created 65 tests just to figure out what's slowing stuff down. It would be cool if there was a place like on github to share with everyone and collate results so that we're not all coding in the dark.
Here are my findings based on those tests. Stuff that kills sprite performance.
1. fill rate! large graphics that need to be updated use smaller sprites (scaling up affects fill rate, scaling down improves perfromance)
2. resorting of sprites (create / destroy, toBack, toFront) do this a lot and it will slow things down a lot
3. accessing x, y, rotation, xScale, yScale is very slow (use translate, rotate and scale and cache the properties for update, see my earlier post for an example)
4. avoid destroying your sprites, instead make them invisible (isVisible = false or alpha = 0) Toggling sprite visibility has some overhead, but may really improve performance.
5. if your sprites are hidden, pause their playback if you can. (you can have 10k hidden sprites not playing and get 60fps, but only 400 hidden sprites playing at 60fps)
6. if the 300 uma sprites are visible when in the viewport, what is performance like when they are out of the view port are they culled? performance improves by about 10 fps when they are offscreen (would doing manual culling help? needs to tested)

All my sprite tests were done with the uma sprite sheet tested on 4th gen ipod touch.

I assume this will extend to any display object, but I haven't confirmed this since my entire game is using only sprites.

Further useful test should be done with physics, timers, variable scoping (globals are slow, but how local is local? what if you access a variable local to a file from inside a function?)
uid: 27183 topic_id: 18550 reply_id: 71294

  • stefano.figurelli likes this

[TOPIC: post.html]
#20

jose2

[GLOBAL: userInfoPane.html]
jose2
  • Contributor

  • 108 posts
  • Corona SDK

I made a mistake. I don't use transition.to in enterframe but i have several loops using it:

Normally in my game swaps around 40 sprites using transition.to loops:

local a1
local a2

function a1()
  transition.to (ball, {xScale =0.9, yScale=1.1, onComplete=a2, time=200})
end

function a2()
  transition.to (ball, {xScale =1.1, yScale=0.9, onComplete=a1, time=200})
end

a1()


I do this to achieve a bounce effect in balls and every ball loops it.

A better way to optimize this ?
uid: 55808 topic_id: 18550 reply_id: 71299


[TOPIC: post.html]
#21

@RSCdev

[GLOBAL: userInfoPane.html]
@RSCdev
  • Corona Geek

  • 1,489 posts
  • Corona SDK

Hello Danny,

Would like to say just a big THANK YOU!

Those TIPS are just A.w.e.s.o.m.E! :)

PS: Tip #2 and #6 are wonderful.

Please, let it come even more. haha

Cheers,
Rodrigo.
uid: 89165 topic_id: 18550 reply_id: 71325


[TOPIC: post.html]
#22

don

[GLOBAL: userInfoPane.html]
don
  • Contributor

  • 638 posts
  • Corona SDK

This should be at the top of the post too:

http://developer.anscamobile.com/content/performance-and-optimization
uid: 27183 topic_id: 18550 reply_id: 71329


[TOPIC: post.html]
#23

@RSCdev

[GLOBAL: userInfoPane.html]
@RSCdev
  • Corona Geek

  • 1,489 posts
  • Corona SDK

I agree, sure it should be at the TOP!

+1.

uid: 89165 topic_id: 18550 reply_id: 71332


[TOPIC: post.html]
#24

Danny

[GLOBAL: userInfoPane.html]
Danny
  • Corona Geek

  • 2,597 posts
  • Corona Staff

@Don, @Rodrigo: Your wish is my command :)
uid: 84637 topic_id: 18550 reply_id: 71337


[TOPIC: post.html]
#25

@RSCdev

[GLOBAL: userInfoPane.html]
@RSCdev
  • Corona Geek

  • 1,489 posts
  • Corona SDK

wOw!

@Danny, very kind of you doing so as it can be even more helpful (besides your own Tips) for many others Corona Members as well as it`s for myself! :)

Thanks!

Cheers,
Rodrigo.
Twitter: @RSCdev
uid: 89165 topic_id: 18550 reply_id: 71338



[topic_controls]
Page 1 of 4 1 2 3 »
 
[/topic_controls]