Lua ‘local’ performance

The ‘local’ keyword in Lua is used to restrict the visibility of a variable in the Lua compiler, however it also affects how the code is executed by the VM. Ultimately this can have a significant impact on how efficiently the variable is accessed. Here are 4 simple examples with big performance differences due to the use of local.

Example 1

function bar()
end
 
function foo()
	bar()
end

In this case ‘bar’ is a global variable. Global variables are stored in a hash table, so every time bar is called a look up is performed. Although hash table look up is well optimized in Lua, this is not a simple operation.

Example 2

local function bar()
end
 
function foo()
	bar()
end

Here ‘bar’ is an up value of ‘foo’. If bar is only being used by foo (or by other functions in this file), this approach is better than Example 1 since we’re not polluting the global namespace. From a performance standpoint it’s also better; in this case bar will be stored as an up value to foo which allows it to be accessed directly by a pointer. This inner body of this function runs about 1.25x faster than Example 1.

Performance tuned Lua code will often cache a frequently used global function into a local variable to achieve a similar same benefit.

Example 3

function foo()
	local function bar()	
	end
	bar()
end

If ‘bar’ is used only by foo, it may be tempting to make it a local variable of foo as shown here. This however has some significant performance issues. Although bar is a local variable to the
function (which is efficiently accessed by index), it will be initialized every time the VM enters foo. This means a new function closure will be allocated every time foo is called and more
garbage will be created. The body of this function will run about 2.7x slower than Example 1.

Creating a new closure every time may seem unnecessary, however functions are mutable in Lua (by calling setfenv for example) so it’s required for correct semantics.

Example 4

do
	local function bar()
	end
	function foo()
		bar()
	end
end

Finally, if you really want to make ‘bar’ only accessible by ‘foo’, you can do it as shown in Example 4. Here bar will be an up value to foo, so the performance is the same as in Example 2.

Leave a Reply