Performance and Optimization

As you develop your application, you should always consider how your design choices affect the performance of your application. Despite recent improvements in computing power, mobile devices still face fundamental constraints in processing power, memory usage, and battery life. Therefore, it's best to think of performance and optimization not only in achieving faster response times but also in minimizing memory usage and maximizing battery life.

In addition to this page, please view the Corona SDK Memory Leak Prevention 101 article from the blog for more memory management tips.

Using Memory Efficiently

Memory is a critical resource on mobile devices. Some devices may even forcibly quit your application if you consume too much of it.

  • Eliminate memory leaks. Your application should not have any memory leaks. Allowing leaks to exist means your application may not have the memory it needs later. Although Lua does automatic memory management, memory leaks can still occur in your code (see Memory Allocation). For example, global variables are never considered garbage; it is up to you to tell Lua that these variables are garbage by nil -ing them out ( globalVar = nil ). If a global variable is a table, then any items in that table will not be considered garbage until you nil them out ( globalVar.item = nil ).
  • Make 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 should be made as small as possible. For example, it's often tempting to load multiple fullscreen-sized images to create flipbook-style animation, even though only elements in the foreground change. In such situations, it’s much better to separate the background into a single fullscreen-sized images and animate the foreground using much smaller images.
  • Load resources lazily. Avoid loading resource files until they are actually needed. Intuitively, prefetching resource files might seem like a good way to save time; however, this practice can actually backfire slowing down your application, because of the way a device responds to low-memory situations. In addition, your application may never even use the resource, making the prefetch a waste of time and memory.
  • Remove objects from the display hierarchy. When a display object is created, it is implicitly added to a display hierarchy. When you no longer need a display object, you should remove it from the display hierarchy, especially then the objects contain images. This makes the display object eligible for garbage collection. However, this is no guarantee that the object will be considered garbage because other variables (not considered garbage) might be referencing it. In this case, the object will not render to the screen, but its memory isn't freed either.

Example

Below is an example of how a memory leak can occur. The code on the left removes the rectangle from the display hierarchy once you tap on it, but the memory used by the rectangle leaks because the variable rect that still refers to it. Because rect is a global variable, the display object it references will not be freed even though the rectangle no longer renders on the screen.

One way to fix this is to modify the removeOnTap function adding a line to nil-out the reference ( rect = nil ). The problem with this approach is that you can no longer use this function for other objects. Instead, you’d have to duplicate the code for that function differing only in one line. A better solution is to simply make the global variable a local one, so you no longer have to explicitly nil-out the reference. The code on the right fixes this by turning rect into a local variable.

Bad (Memory Leak) Better
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 -- rect is a global variable
rect = display.newRect(0,0,10,10)
rect:setFillColor(255,255,255)
 
local function removeOnTap( event )
   local t = event.target
   local parent = t.parent
 
   -- remove from display hierarchy
   -- but var "rect" still exists
   -- so memory is never freed
   parent:remove( t )
 
   return true
end
 
rect:addEventListener(
   "tap", removeOnTap ) 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- rect is a local variable
local rect = 
   display.newRect(0,0,10,10)
rect:setFillColor(255,255,255)
 
local function removeOnTap( event )
   local t = event.target
   local parent = t.parent
 
   -- remove from display hierarchy
   parent:remove( t )
 
   return true
end
 
rect:addEventListener(
   "tap", removeOnTap ) 

Reducing Power Consumption

Battery life is inherently limited on mobile devices because of their small form factor. You can help improve battery life by minimizing the use of the following features:

  • Network traffic (Wi-Fi radios and baseband cell radios)
  • GPS
  • Accelerometers
  • Disk accesses (reading/writing to files)
  • You will inevitably use these features to create great user experiences. However, as you design your application, make judicious use of these features, as everything you do impacts the battery life of the user's device

Network

Of all the activities, network accesses consume the most power. You can minimize the impact of network traffic by following these guidelines:

  • Do not poll. Connect to external network servers only when needed.
  • Minimize data size. Optimize the data you transmit so that it is as small as possible.
  • Transmit in bursts. More power is consumed the longer the radio is actively transmitting data. Therefore, transmit the data in bursts instead of spreading out the same data into smaller transmission packets over time.
  • If you access location data, stop collecting location update events as soon as you have the data you need. Location data comes from using GPS, cell, and Wi-Fi networks, so the best way to save power is to only collect location data when you need it.

CPU

Another way to minimize power consumption is to optimize the running time of your application. Some rules of thumb include performing work lazily on an as needed basis rather than doing work that ends up being unused.

Graphics

Group objects

If you are going to set the property (such as alpha) of a bunch of objects to the same value, it’s preferable to add the objects to a group and then modify the property of the group. It’s easier for you to code and it optimizes your animation.

Another benefit of organizing objects into groups is to organize your content into screens (see Managing Screens).

Turn off animations for non-visible objects

It may sound obvious, but it’s often easy to overlook the fact that you may have animations running that are invisible or that are offscreen.

For example, you might have a group that stores all objects for the main menu. In the main menu group, there are several child objects that you animate (perhaps a bouncing ball or a rotating gear) by registering a listener for "enterFrame" events to achieve custom animation. When the user goes to another screen, you set the group to be invisible ( isVisible set to false). Unfortunately, the listener will continue to do calculations that don’t produce any visible effect. See Pausing and Restarting Animations.

The solution is to remove the event listener when you change to a new screen and then re-register the listener when you re-enter the menu screen.

Optimize image sizes

Be careful when using large images, especially fullscreen images. They impact performance in two ways:

First, they take more time to load so they can impact the responsiveness of your application.

Second, they use up a lot of memory; some devices will even force quit your application if too much memory is consumed. Therefore, you should remove them from their parent group when you no longer need them:

  
local image = display.newImage( "image.png" )
 
-- do stuff with image
 
image:getParent():remove( image )
 

Minimize setup code at startup time

When your application launches, your main.lua file will typically contain a lot of setup code to add images to the screen, set up listeners to respond to user events or frame events, etc. If your setup code takes too long, your users will not see any screen updates because no screen update can occur until after a code block has finished executing.

Lua: Best Practices

Simple changes to your Lua code can yield tremendous benefits. Below are some examples of ways to squeeze out extra performance in your Lua code using very simple coding changes:

Use locals (i.e. avoid global variables)

Avoid global variables. Period. In Lua, you will sacrifice performance if you use global variables. When in doubt, precede your variable declarations with local .

This applies even to functions. In Lua functions are variables too. In long loops, it’s better to assign a function to a local variable. In the following example, the code on the left runs 30% slower than the one on the right!

Global Local
1
2
3
for i = 1, 1000000 do 
   local x = math.sin(i) 
end  
1
2
3
4
local sin = math.sin 
for i = 1, 1000000 do 
   local x = sin(i) 
end 

In some situations, you may not be able to cache a global variable in a pure local variable. For example, inside a function, you may not be able to cache the function as a variable local to that function. In this situation, you can use local variables that are outside the function scope, i.e. external locals. External locals are not as fast as pure locals, but are faster than globals.

In the following example, we can optimize the code on the left by declaring sin once outside function foo :

Global External local
1
2
3
4
5
6
 function foo (x) 
   for i = 1, 1000000 do 
      x = x + math.sin(i) 
   end 
   return x 
end  
1
2
3
4
5
6
7
local sin = math.sin 
function foo (x) 
   for i = 1, 1000000 do
      x = x + sin(i) 
   end 
   return x 
end 

Again, the global version runs 30% slower.

Further reading: Significance of Local Variables in Lua

Math: fast vs slow

Multiplication x*0.5 is faster than division x/2 .

x*x is faster than x^2

Inserting objects into arrays

Short inline expressions can be faster than function calls. For example, when appending item to an array t the following t[#t+1] = item is much faster than table.insert( t, item ) .

Constant Folding

Constant folding is the process of simplifying constant expressions at compile time. For example, the statement i=111+111 will be just as fast as i=222 because the compiler can precalculate the value of that expression.

To take advantage of this, you need to be aware of when the compiler can do such calculations and when it cannot. First, the compiler is not smart enough to know that values inside variables are constant even if you consider those variables to be constant in your code. Second, because of associativity rules, the ordering of constants matters. Lua considers a+b+c to be equivalent to (a+b)+c . Therefore, the expression 1+2+x will be treated as (1+2)+x and be optimized to 3+x, but x+1+2 will be treated as (x+1)+2 and thus not be optimized.

Cache properties in a local variable

If you are constantly accessing a property of a table but not changing its value, then you should cache that value. There is a slight performance penalty to doing property lookups in a table. For objects created by Ansca's api's — such as the display object returned by display.newImage() - the penalty is even higher.

Uncached Cached
1
2
3
4
5
6
7
8
 function foo (o)
   local n = 0
   for i = 1, 1000000 do
      -- lookup o.x each time 
      n = i + n * o.x
   end
   return n
end  
1
2
3
4
5
6
7
8
9
 function foo (o)
   -- cache o.x before the loop
   local x = o.x
   local n = 0
   for i = 1, 1000000 do 
      n = i + n * x
   end 
   return n
end