As you develop your application, you should always consider how your design choices affect performance. Despite ongoing core improvements, mobile devices still face fundamental constraints in processing power, memory usage, and battery life. Therefore, performance and optimization is crucial to achieving faster response time, minimizing memory usage, and maximizing battery life.
Memory is a critical resource on mobile devices. Some devices may even terminate your application if you consume too much memory.
nil
.Global variables are not recommended in general, but if you must use them for convenience, ensure that you remove them from memory when they're no longer needed nil
)
Keep resource files as small as possible — resource files used by your application typically reside on the disk. They must be loaded into memory before they can be used. Images and audio files should be as small as possible. You should also reuse the same image assets whenever it's feasible. For example, if you design a
Load resources lazily — avoid loading resource files until they're actually needed. While
Remove objects from the display hierarchy — when a display object is created, it is implicitly added to the display hierarchy. When you no longer need a display object, you should remove it from the display hierarchy and set its reference to nil
. This makes the object eligible for garbage collection. However, this is no guarantee that the object will be removed from memory. If other variables in memory reference the display object, Lua will not consider it garbage. See the Display Objects guide for more information on removing objects.
Battery life is inherently limited on mobile devices because of their small form factor. You can improve battery life by adhering to the following practices.
Network access consumes a considerable amount of power. You can minimize the impact of network traffic by following these guidelines:
GPS and accelerometer hardware also consumes power. If you access location data via GPS, stop collecting it when you have the data you need. If you use the accelerometer, try to limit it to scenes where it's essential for the desired functionality.
Disk access — reading and writing files to the device's local disk — should be handled similarly to network access. It's better to transmit larger packets of data to/from the disk rather than spreading it out over numerous smaller transactions.
If you need to set or transition/tween a specific property of several display objects to the same value — for example, fade an entire overlay menu alpha=0
—
If you're using sprite animations, a common oversight is allowing offscreen or invisible sprites to continue animating. While these sprites may not be visible to the user, they'll continue to use processor power while animating. We suggest that you pause all animations that move off the screen or otherwise become inactive.
Texture memory is often ignored until it reaches "critical mass," at which point it's
As general practice, remember these tips in respect to managing texture memory:
Always unload textures (remove them from the display hierarchy) when they're no longer needed.
If you're using image sheets, consider using a tool like TexturePacker to pack your images into the smallest configuration possible.
There is a limit on the maximum texture size that a device will support. If you exceed this limit, the texture will automatically downscale to fit within the maximum. You can use the system.getInfo( "maxTextureSize" )
On devices, OpenGL performs best when you are able to minimize state changes. This is because multiple objects can be batched into a single draw call if there are no state changes required between consecutive display objects.
Solar2D's rendering engine attempts to identify situations where multiple display objects can be submitted in a single draw call. Whenever possible, you should try to arrange the display object hierarchy such that consecutive display objects — meaning, the order in which they are rendered — can be batched into a single draw call.
There are certain situations where this can occur. The general rule is that consecutive display objects which use the same texture can be batched. This includes display objects that use different frames from the same image sheet, since the underlying texture is the same. In these situations, you can vary the position, tint, and alpha of each object without breaking the batch, but remember that some actions may prevent batching, for example adding a shader effect to an object.
At the code level, you should adhere to as many Lua optimizations as possible. Most of the performance tricks below pertain primarily to
In contrast to global variables, which should be avoided whenever possible, access to local variables and functions is simply faster, especially in
-- Local (recommended) local CCX = display.contentCenterX -- Local variable for i = 1,100 do local image = display.newImage( "myImage.png" ) image.x = CCX end
-- Non-local (discouraged) CCX = display.contentCenterX -- Global variable for i = 1,100 do local image = display.newImage( "myImage.png" ) image.x = CCX end
This also applies to core Lua libraries like the math
library. In
-- Local (recommended) local sin = math.sin -- Local reference to "math.sin" local function foo(x) for i = 1,100 do x = x + sin(i) end return x end
-- Non-local (discouraged) local function foo( x ) for i = 1,100 do x = x + math.sin(i) end return x end
Finally, remember that functions should be localized whenever possible. Of course, this will require proper scoping!
-- Local (recommended) local function func2( y ) -- "func2()" properly scoped above "func1()" print( y ) end local function func1() func2( "myValue" ) end func1()
-- Non-local (discouraged) function func1() func2( "myValue" ) end function func2( y ) print( y ) end func1()
Let's compare four methods that all achieve the same thing: the common act of inserting values into a table. Of the four, the Lua table.insert()
function is a mediocre performer and should be avoided.
-- Loop index method (recommended) local a = {} for i = 1,100 do a[i] = i end
-- Counter method (recommended) local a = {} local index = 1 for i = 1,100 do a[index] = i index = index+1 end
-- Table size method (acceptable) local a = {} for i = 1,100 do a[#a+1] = i end
-- "table.insert()" (discouraged) local a = {} for i = 1,100 do table.insert( a, i ) end
The Lua unpack()
function is not a great performer. Fortunately, a simple loop can be written to accomplish the same thing:
-- Loop method (recommended) local a = { 100, 200, 300, 400 } for i = 1,100 do print( a[1],a[2],a[3],a[4] ) end
-- "unpack()" (discouraged) local a = { 100, 200, 300, 400 } for i = 1,100 do print( unpack(a) ) end
The caveat is that you must know the length of the table to retrieve all of its values in the loop method. Thus, unpack()
still has its uses — in a table of unknown length,
When iterating through a table, the overhead of the Lua ipairs()
function does not justify its use, especially when you can accomplish the same thing using a Lua construct.
-- Lua construct (recommended) local t1 = {} local t2 = {} local t3 = {} local t4 = {} local a = { t1, t2, t3, t4 } for i = 1,#a do print( a[i] ) end
-- "ipairs()" (discouraged) local t1 = {} local t2 = {} local t3 = {} local t4 = {} local a = { t1, t2, t3, t4 } for i,v in ipairs( a ) do print( i,v ) end
Certain mathematical functions and processes are faster than others. For example, multiplication is faster than division and you should usually multiply by a decimal instead of dividing.
-- Multiplication by decimal (recommended) x * 0.5 y * 0.125
-- Division (acceptable) x / 2 y / 8
Multiplication is also faster than exponentiation:
-- Multiplication (recommended) x * x * x
-- Exponentiation (acceptable) x^3
Finally, avoid math.fmod()
for positive numbers and use the modulus operator instead:
-- Modulus operator (recommended) for i = 1,100 do if ( ( i%30 ) < 1 ) then local x = 1 end end
-- "math.fmod()" (discouraged) local fmod = math.fmod for i = 1,100 do if ( fmod( i,30 ) < 1 ) then local x = 1 end end
When using audio, you should compress/sample sounds to the smallest acceptable quality in most cases. Also, using simple, .wav
do not tax the CPU heavily.
Sound effects for an app should almost always be preloaded in
If desired, sound effects can be organized in a table as follows, for easy reference and eventual disposal.
local soundTable = { mySound1 = audio.loadSound( "a.wav" ), mySound2 = audio.loadSound( "b.wav" ), mySound3 = audio.loadSound( "c.wav" ), mySound4 = audio.loadSound( "d.wav" ), mySound5 = audio.loadSound( "e.wav" ), mySound6 = audio.loadSound( "f.wav" ), mySound7 = audio.loadSound( "g.wav" ), mySound8 = audio.loadSound( "h.wav" ), }
With this structure, playback is as simple as:
local mySound = audio.play( soundTable["mySound1"] )
Remember, you must dispose of audio files when they're no longer needed and clear any references to them. Assuming the table structure above is used to organize and preload sounds, the following loop will dispose of the audio handles:
for s = #soundTable,1,-1 do audio.dispose( soundTable[s] ) ; soundTable[s] = nil end