Posted on by

Steffen Itterheim is no stranger to development. He is a former software engineer/manager at Germany’s Electronic Arts division and author of the book Learn iPhone and iPad Cocos2D Game Development.

Though he is proficient in a multitude of languages, Steffen says his favorite mobile programming script is Lua. For more specific details, we have given him the floor below to illustrate why he prefers Lua for mobile development.

Steffen currently is an independent app and game developer. You can follow him on Twitter and check out his website for more info.


Lua, like most languages, allows you to do specific tasks in a variety of ways. Usually some ways are more efficient and thus faster. In this blog post I’ll show you how you can squeeze out the maximum performance when working with Lua.

Iterating over a table can be done in a lot of slightly different ways. Lua offers two convenient functions pairs and ipairs to iterate over table elements. The following example shows how you can iterate with ipairs over an indexed table to play sound effects in Corona. Maybe not the most relevant example but it gets the idea across.

[cc lang="lua"]
for i, sfx in ipairs(myTable) do
media.playEventSound(sfx)
end
[/cc]

The caveat: using ipairs to iterate over an indexed table takes more than twice as much time as iterating from 1 to the length of the table. The length of a table can be obtained by prefixing the table with the # operator. The # operator only counts indices, if there were other keys in the table those would not be counted.

In the next example, ipairs is removed from the for loop and instead myTable[i] is assigned to the sfx variable. Moreover, the sfx variable is declared local because variables declared as local are quite a bit faster to access than global variables. How much faster varies a lot but it’s always faster to use a local variable compared to one that’s not local, even if it requires adding another statement just to declare a variable as local.

[cc lang="lua"]
for i = 1, #myTable do
local sfx = myTable[i]
media.playEventSound(sfx)
end
[/cc]

Once you realize that the local variable str is reused every iteration, it’s a simple step to move the definition of the str variable outside the for loop. Now the variable is declared once outside the loop, and then only a simple assignment is made during each iteration. This doesn’t make much of a difference, but sometimes every little bit helps:

[cc lang="lua"]
local sfx
for i = 1, #myTable do
sfx = myTable[i]
media.playEventSound(sfx)
end
[/cc]

And now we can even do the same for the media.playEventSound() method provided by Corona. Functions themselves are also stored in variables, or as in this case in the media table, and thus can be declared local as well. Assigning media.playEventSound to the local variable playSound gives us another speed boost, and the more sounds are played the more pronounced the difference will be:

[cc lang="lua"]
local playSound = media.playEventSound
local sfx
for i = 1, #myTable do
sfx = myTable[i]
playSound(sfx)
end
[/cc]

Notice that in the assignment to playSound the media.playEventSound is written without brackets. You don’t want to actually call the function and assign its result to playSound. Instead, you want to assign the variable storing the function object to the local playSound variable.

Other optimization techniques include avoiding table.insert, as in this example:
[cc lang="lua"]
for i = 1, 1000 do
table.insert(myTable, i)
end
[/cc]

This can be 5 to 10 times slower than an assignment using the table length operator:

[cc lang="lua"]
for i = 1, 1000 do
myTable[#myTable + 1] = i
end
[/cc]

Another issue to watch out for is called thrashing the garbage collector. The following, innocent looking example does exactly that. The loop creates a lot of objects that are no longer in use after the loop, causing the garbage collector to do some heavy lifting which can cause intermittent framerate drops.

[cc lang="lua"]
myString = “0″
for i = 1, 1000 do
myString = myString .. tostring(i)

tempTable = {count = i}
– use tempTable
end
[/cc]

First of all, this creates 1000 strings and 999 of them will be garbage collected after the loop is done. Strings are immutable in Lua, so every change to an existing string actually creates a new string. String concatenation is not something you should do inside a loop, especially not during gameplay. Likewise, a new instance of the tempTable table is created once per iteration, even though the same table could have been re-used.

The following example is more friendly to the garbage collector. The strings are first added to a myStrings table one by one, and the concatenation is done after the loop and only once, using the optimized table.concat function. And tempTable is now declared outside the for loop with its count key simply being re-assigned during each iteration:

[cc lang="lua"]
myStrings = {“0″}
tempTable = {count = 0}

for i = 1, 1000 do
myStrings[#myStrings + 1] = tostring(i)

tempTable.count = i
– use tempTable
end

myString = table.concat(myStrings)
[/cc]

To summarize, it is good practice in performance critical loops to localize functions and variables used inside the loop, and to avoid using the pairs/ipairs and table.insert functions. You also want to avoid thrashing the garbage collector by being mindful about when and where new objects are being created, specifically string concatenation is a frequent source of creating garbage objects to be removed by the garbage collector.

But do keep in mind that your first goal should be to write readable and maintainable code, and the root of all evil is premature optimization. Especially if you don’t know exactly what’s causing a slowdown. It could just as easily be a simple matter of too many sprites on screen, or too many physics bodies generating a lot of collisions.

This post only scratched on the surface of optimizing Lua code for best performance. If you’d like to see some actual test results, I can recommend this page with Lua performance results. You may also find the Optimization Tips on the Lua wiki insightful.


Posted by . Thanks for reading...

7 Responses to “Guest Post: Steffen ItterheimAuthor, Indie Game Developer, and Lua Advocate”

  1. Magenda

    Awesome tips!

    But, how someone could avoid using pairs ?
    For example, what would be a possible optimization for the following code?


    local tags={one=3, two=1, three=9}
    for k,v in pairs(tags) do
    print(tags[k])
    end

    Also, isn’t there any performance gain from changing this:

    local a=myfunc1()
    local b=myfunc2(a)

    to this:

    local b=myfunc2( myfunc1() )

    …inside a loop?

    Reply
  2. Jonathan Beebe

    Great advice Steffen, ever since discovering your blog a couple days ago (learn-corona.com), I’ve been consistently getting great Lua advice from you. Looking forward to reading more of your stuff, and to also see what you produce in Corona!

    Reply
  3. GamingHorror

    @Magenda:
    You can’t avoid pairs() in your case. The point is that keyed tables are great for lookup if you know the names of the keys, but if you mostly iterate over them, you’re better off using an indexed table.

    In this case an indexed table is used, and below I defined a few variables which replace the keys, so that you can still write tags[two] which is similar to writing tags.two in case you really want to be able to access values by name as well.

    local tags={3, 1, 9}
    for k,v in #tags do
    print(tags[k])
    end

    local one = 1, two = 2, three = 3
    secondTag = tags[two]

    I’m pretty sure that both versions of the function calls create the same instructions. This is true for almost all languages. For example, in C variants there’s no difference between:

    int result1 = funcA();
    int result2 = funcB(result1);

    and

    int result2 = funcB(funcA());

    The compiler automatically adds the result1 variable because some place in memory is needed to store the result from funcA anyway, so there’s no harm done in writing the variable out. In fact, it’s considered good practice to avoid nesting function calls, as it tends to make the code harder to read.

    Reply

Leave a Reply

  • (Will Not Be Published)