Ludicrous Software

Performance of Local v. Table Functions

Short version: If you’re creating a Lua module with publicly accessible functions, just add the functions as properties of the table you’re returning, rather than as local functions with a table reference. Now for the long(er) version…

Update: added one more example for further context. It’s at the bottom of the post.

While working on an app that uses a particularly popular Corona library I encountered a forward declaration bug in the library. If you’ve been using Corona/Lua for any length of time, you’ve probably encountered this kind of bug before. It occurs when you try to reference a local variable that you haven’t yet declared. It looks something like this:

1
2
3
4
5
6
myFunction printA()
  print(a)
end

local a = "5"
printA()

The problem is that the printA() function is referencing the local variable a, but a wasn’t declared before printA(). So, not having found a local variable a, it looks for a global variable with the same name. If that exists, then it prints out the value of that variable (which probably isn’t your intent), or you get a nil value error. The easy way to avoid this kind of error is to declare all your variables at the top of your chunk of code, before they’re referenced.

The actual forward reference bug in the library was a little more involved, because the local functions were also assigned to properties of the table that the library returns, because the functions need to be publicly accessible. It looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
myTable = {}

local function functionA()
  functionB()
end
myTable.functionA = functionA

local function functionB()
  -- whatever
end
myTable.functionB = functionB

return myTable

So you can probably spot the forward declaration bug. This could have been fixed by a bunch of forward declarations. But in thinking about this, I wondered if that would even be worthwhile. All of the functions are being added as properties of the table, so why not just do this:

1
2
3
4
5
6
7
8
9
10
myTable = {}
function myTable.functionA()
  myTable.functionB()
end

function myTable.functionB()
  -- whatever
end

return myTable

I don’t know what the developer was really thinking about, but I suspect that the answer is “performance”. Since Lua is able to look up local variables faster than table properties, referencing the functions via local variables is presumably faster for function calls from within the module.

So I wonder if that is really true.

Or if it is, whether the speed difference makes the extra effort involved in the ‘local’ approach to be worthwhile.

And it’s not. At least, not enough to matter.

To test this, here’s the sample app I whipped up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local myTable = {}

local function stress()
  local i = 1 + 1
end

myTable.stress = stress

-- test calling the local function
local startTime = os.time()
for i = 1, 10000000 do
  stress()
end
print("stress(), elapsed ", (os.time() - startTime))

-- test calling the table function
startTime = os.time()
for i = 1, 10000000 do
  myTable.stress()
end
print("myTable.stress(), elapsed ", (os.time() - startTime))

So here’s what the code does:

  1. Create a local function;
  2. Store a reference to that function in a table;
  3. Time how long it takes to call that local function;
  4. Time how long it takes to call the function via the table property.

And the result when I built this and tested it on my iPhone 4: 7 milliseconds for the local function calls, and 9 milliseconds for the table function calls. Now, you may be thinking that that is actually pretty significant, but take a look at how many times I had to call the functions to get that difference: 10,000,000 times.

Ten million times. To save two milliseconds

Now, just to be clear, I’m a big fan of optimizing code up front in a lot of cases. Having started my development life in the world of Flash Lite, that sort of behaviour is ingrained. But even for me, it’s tough to see the benefit to the “local and table” approach. If there’s a value in that approach that I’m not quite seeing, please let me know what it is!

Update: I just wanted to expand on my comments above. Initially, I had meant this comparison of the execution of local and table functions to be applicable within a limited context: when you’re creating a local function that you’re planning to make public via the table. To be clear, my point is that there is no reasonable benefit to be gained from making that function local in the first place. Just skip that step altogether.

But as I thought about it more, I started to wonder about the general wisdom of the usual approach of making local references to table functions to increase performance speed. For example, doing this sort of thing:

1
2
local asin = math.asin
local myVar = asin(43)

This is the usual recommendation, and at first I feared that what I’d written above might be construed as contradicting that general wisdom. But it turns out that I’m not sure there’s enough benefit to be gained from doing this. Here’s the sample code:

1
2
3
4
5
6
7
8
9
10
11
12
startTime = os.time()
for i = 1, 5000000 do
  local j = math.floor(4.35)
end
print("math.floor, elapsed ", (os.time() - startTime))

local floor = math.floor
startTime = os.time()
for i = 1, 5000000 do
  local j = floor(4.35)
end
print("floor, elapsed ", (os.time() - startTime))

Turns out that over the course of 5,000,000 calls to the local function, a grand total of one millisecond is saved. Again, this is tested on an iPhone 4. I realize that a) these results may vary quite a bit on different devices, so thorough testing on all targets should be undertaken, and b) in other contexts, where you may in fact be handling that many function calls (e.g. if you’re using Lua in a non-mobile setting, perhaps), local references to table functions may be worthwhile.

But, for my part, I think I’ll probably skip it, at least until the point where I think it might actually help when implemented as part of a broader range of optimizations.