optimizationscriptingluapremature-optimization

Why would this Lua optimization hack improve performance?


I'm looking over a document that describes various techniques to improve performance of Lua script code, and I'm shocked that such tricks would be required. (Although I'm quoting Lua, I've seen similar hacks in Javascript).

Why would this optimization be required:

For instance, the code

for i = 1, 1000000 do 
   local x = math.sin(i) 
end

runs 30% slower than this one:

local sin = math.sin 
for i = 1, 1000000 do
    local x = sin(i) 
end

They're re-declaring sin function locally.

Why would this be helpful? It's the job of the compiler to do that anyway. Why is the programmer having to do the compiler's job?

I've seen similar things in Javascript; and so obviously there must be a very good reason why the interpreting compiler isn't doing its job. What is it?


I see it repeatedly in the Lua environment I'm fiddling in; people redeclaring variables as local:

local strfind = strfind
local strlen = strlen
local gsub = gsub
local pairs = pairs
local ipairs = ipairs
local type = type
local tinsert = tinsert
local tremove = tremove
local unpack = unpack
local max = max
local min = min
local floor = floor
local ceil = ceil
local loadstring = loadstring
local tostring = tostring
local setmetatable = setmetatable
local getmetatable = getmetatable
local format = format
local sin = math.sin

What is going on here that people have to do the work of the compiler? Is the compiler confused by how to find format? Why is this an issue that a programmer has to deal with? Why would this not have been taken care of in 1993?


I also seem to have hit a logical paradox:

  1. Optimization should not be done without profiling
  2. Lua has no ability to be profiled
  3. Lua should not be optimized

Solution

  • Why would this be helpful? It's the job of the compiler to do that anyway. Why is the programmer having to do the compiler's job?

    Lua is a dynamic language. Compilers can do a lot of reasoning in static languages, like pulling constant expressions out of the loop. In dynamic languages, the situation is a bit different.

    Lua's main (and also only) data structure is the table. math is also just a table, even though it is used as a namespace here. Nobody can stop you from modifying the math.sin function somewhere in the loop (even thought that would be an unwise thing to do), and the compiler cannot know that when compiling the code. Therefore the compiler does exactly what you instruct it to do: in every iteration of the loop, lookup the sin function in the math table and call it.

    Now, if YOU know that you are not going to modify math.sin (i.e. you are going to call the same function), you can save it in a local variable outside the loop. Because there are no table lookups, the resulting code is faster.

    The situation is a bit different with LuaJIT - it uses tracing and some advanced magic to see what your code is doing in runtime, so it can actually optimize the loop by moving the expression outside of the loop, and other optimizations, apart from actually compiling it to machine code, making it crazy fast.

    Regarding the the 'redeclaring variables as local' - many times when defining a module, you want to work with the original function. When accessing pairs, max or anything using their global variables, nobody can assure you that it will be the same function every call. For example stdlib redefines a lot of global functions.

    By creating a local variable with the same name as the global, you essentially store the function into a local variable, and because local variables (which are lexically scoped, meaning they are visible in the current scope and any nested scopes too) take precedence before globals, you make sure to always call the same function. Should someone modify the global later, it will not affect your module. Not to mention it is also faster, because globals are looked up in a global table (_G).

    Update: I just read Lua Performance Tips by Roberto Ierusalimschy, one of Lua authors, and it pretty much explains everything that you need to know about Lua, performance and optimization. IMO the most important rules are:

    Rule #1: Don’t do it.

    Rule #2: Don’t do it yet. (for experts only)