Things you should know about Lua's performance

This wiki is a result of some lua performance tests (the widget is included with ca sandbox).


TEST 1: localize

Code:

local min = math.min

Results:

normal way: 0.719 (158%)
localized: 0.453 (100%)

Conclusion:

  -> Yeah, we should localize all used funtions.

TEST 2: localized class-methods (with only 3 accesses!)

Code1:

  for i=1,1000000 do
    local x = class.test()
    local y = class.test()
    local z = class.test()
  end

Code2:

  for i=1,1000000 do
    local test = class.test
    local x = test()
    local y = test()
    local z = test()
  end

Results:

normal way: 1.203 (102%)
localized: 1.172 (100%)

Conclusion:

  -> No, it isn't faster to localize a class method IN the function call.

TEST 3: unpack a table

Code1:

  for i=1,1000000 do
    local x = min( a[1],a[2],a[3],a[4] )
  end

Code2:

  local unpack = unpack
  for i=1,1000000 do
    local x = min( unpack(a) )
  end

Code3:

  local function unpack4(a)
    return a[1],a[2],a[3],a[4]
  end
  for i=1,1000000 do
    local x = min( unpack4(a) )
  end

Results:

with [ ]: 0.485 (100%)
unpack(): 1.093 (225%)
custom unpack4: 0.641 (131%)

Conclusion:

  -> Don't use unpack() in time critical code!

TEST 4: determine maximum and set it ('>' vs. max)

Code1:

  local max = math.max
  for i=1,1000000 do
     x = max(random(cnt),x)
  end

Code2:

  for i=1,1000000 do
    local r = random(cnt)
    if (r>x) then x = r end
  end

Results:

math.max: 0.437 (156%)
'if > then': 0.282 (100%)

Conclusion:

  -> Don't use math.[max|min]() in time critical code!

TEST 5: nil checks ('if' vs. 'or')

Code1:

  for i=1,1000000 do
    local y,x
    if (random()>0.5) then y=1 end 
    if (y==nil) then x=1 else x=y end
  end

Code2:

  for i=1,1000000 do
    local y
    if (random()>0.5) then y=1 end 
    local x=y or 1
  end

Results:

nil-check: 0.297 (106%)
a=x or y: 0.281 (100%)

Conclusion:

  -> WOW! the or-operator is faster than a nil-check. Use it! :D

TEST 6: 'x2' vs. 'x*x'

Code1:

  for i=1,1000000 do
     local y = x^2
  end

Code2:

  for i=1,1000000 do
     local y = x*x
  end

Results:

x^2: 1.422 (110%)
x*x: 1.297 (100%)

TEST 7: modulus operators (math.mod vs. %)

Code1:

  local fmod = math.fmod
  for i=1,1000000 do
    if (fmod(i,30)<1) then
      local x = 1
    end
  end

Code2:

  for i=1,1000000 do
    if ((i%30)<1) then
      local x = 1
    end
  end

Results:

math.mod: 0.281 (355%)
%: 0.079 (100%)

Conclusion:

  -> Don't use math.fmod() for positive numbers (for negative ones % and fmod() have different results!)!

TEST 8: functions as param for other functions

Code1:

  local func1 = function(a,b,func) 
    return func(a+b) 
  end

  for i=1,1000000 do
    local x = func1(1,2,function(a) return a*2 end)
  end

Code2:

  local func1 = function(a,b,func) 
    return func(a+b) 
  end
  local func2 = function(a) 
    return a*2 
  end

  for i=1,1000000 do
    local x = func1(1,2,func2)
  end

Results:

defined in function param: 3.890 (1144%)
defined as local: 0.344 (100%)

Conclusion:

  -> REALLY, LOCALIZE YOUR FUNCTIONS ALWAYS BEFORE SENDING THEM INTO ANOTHER FUNCTION!!!
     i.e if you use gl.BeginEnd(), gl.CreateList(), ...!!!

TEST 9: for-loops

Code1:

  for i=1,1000000 do
    for j,v in pairs(a) do
      x=v
    end
  end

Code2:

  for i=1,1000000 do
    for j,v in ipairs(a) do
      x=v
    end
  end

Code3:

  for i=1,1000000 do
    for i=1,100 do
      x=a[i]
    end
  end

Code4:

  for i=1,1000000 do
    for i=1,#a do
      x=a[i]
    end
  end

Code5:

  for i=1,1000000 do
    local length = #a
    for i=1,length do
      x=a[i]
    end
  end

Results:

pairs: 3.078 (217%)
ipairs: 3.344 (236%)
for i=1,x do: 1.422 (100%)
for i=1,#atable do 1.422 (100%)
for i=1,atable_length do: 1.562 (110%)

Conclusion:

  -> Don't use pairs() or ipairs()!
     Try to save the table-size somewhere and use "for i=1,x do"!

TEST 10: array access (with [ ]) vs. object access (with .method)

Code1:

  for i=1,1000000 do
    x = a["foo"]
  end

Code2:

  for i=1,1000000 do
    x = a.foo
  end

Results:

atable["foo"]: 1.125 (100%)
atable.foo: 1.141 (101%)

TEST 11: buffered table item access

Code1:

  for i=1,1000000 do
    for n=1,100 do
      a[n].x=a[n].x+1
    end
  end

Code2:

  for i=1,1000000 do
    for n=1,100 do
      local y = a[n]
      y.x=y.x+1
    end
  end

Results:

'a[n].x=a[n].x+1': 1.453 (127%)
'local y=a[n]; y.x=y.x+1': 1.140 (100%)

TEST 12: adding table items (table.insert vs. [ ])

Code1:

  local tinsert = table.insert
  for i=1,1000000 do
    tinsert(a,i)
  end

Code2:

  for i=1,1000000 do
    a[i]=i
  end

Code3:

  for i=1,1000000 do
    a[#a+1]=i
  end

Code4:

  local count = 1
  for i=1,1000000 do
    d[count]=i
    count=count+1
  end

Results:

table.insert: 1.250 (727%)
a[i]: 0.172 (100%)
a[#a+1]=x: 0.453 (263%)
a[count++]=x: 0.203 (118%)

Conclusion:

  -> Don't use table.insert!!!
     Try to save the table-size somewhere and use "a[count+1]=x"!