Introduction
After spending over a month now testing various codes and formats in computercraft using a profler as well as reading lua optimization guides this is my quick and simple guide for the computercraft community on pushing the most out of their programs.some of these methods offer a 30-90% increase in speeds as opposed to conventional methods used in most programs while others offer a minuscule shave off of run time that may not be worth the extra effort it takes to implement them. Anything over a 20% increase in average speeds i will mark as "MAJOR" so you know which methods to look towards first. If a tip is marked with "WEAK" that means trying to use this will usually result in more hassle than the particular method is worth and if implemented wrong could even cause a decrease in speed.
Before beginning optimization first ask yourself does my program even need optimized. Yes we all want to have an insanely blazing fast program but not all programs are considered time critical. A frame buffer should always be as fast as possible because it is relied on for its speed. A door password on the other hand as long as it has a reasonable speed doesnt need to be blazing fast. After asking yourself if a program needs optimized you also need to ask can I make something faster even before applying any optimization tricks. You will gain a much larger speed difference many times simply by rewriting a program in a more resource friendly way then you will by changing simply how you write and use those resource using optimization hacks. After you have passed those two questions and still decided your program is taking too long to do what you want please follow this guide to push that extra bit out of your code.
This guide will be divided into four parts.
The good practice section is stuff that increase readabilit and have a major effect on speed or have such a massive effect on speed it is enough to completely toss out the impact of readability. These tips should always be followed whenever you are coding anything that they apply to.
The optimization section features tips that can get a decent boost on speed while sacrificing some readability in return. These should only be followed if you feel speed is crucial to your program.
The micro-optimization section contains tips that only offer a minor impact on speed however they will usually hurt readability and functionality a lot of times. They are left here more for academic purposes in case anyone ever does have a reason to micro-optimize. Remember the first law of micro optimization: Don't do it.
The misinformation section contains tips that either were at one time helpful for optimization and now have no impact or a negative impact as well as tips that tend to be misinformed or just bad practices people tend to pick up when trying to improve their codes. This is essentially a section of things you SHOULD NOT DO
It should also be noted this guide ONLY applies to Lua in Computercraft in conjecture with the latest version. Old outdated versions, other lua platforms, and computercraft emulators may not benefit in the same way from these tips. If you want to apply these to a platform outside of minecraft computercraft please test them before assuming it still applies.
Good Practice
MAJOR: use local variables
local foo = "Hello World!"
print(foo)
is faster than
foo = "Hello World"
print(foo)
this also applies to using global variables. You can make these local to gain a large (roughly 30%) speed increase in your program
--#this is slower
local foo = os.time()
for i=1,100000
x = math.sin(i)
end
print(os.time()-foo)
sleep(0.0)
--#this is faster
local sin = math.sin
foo = os.time()
for i=1,100000
x = sin(i)
end
print(os.time()-foo)
why?
when accessing a local variable on the machine code side of lua there is only 1 line of code that it needs to pass through as. If our lua code was
a = a + b
if a and b are both local and assuming they are 0 and 1 in the registry respectively then lua runs
ADD 0 0 1
however if a and b are both global values this becomes
GETGLOBAL 0 0
GETGLOBAL 1 1
ADD 0 0 1
SETGLOBAL 0 0
it is easy to see how this would build up very quickly on larger programs.MAJOR: pre-index tables
I consider this the most important optimization tip in this guide because I have never seen a program take this step and it offers one of the most beneficial increases in speed (i recorded up to a 90% speed increase in my profiler). Essentially you should start a table with the number of indexes you expect it to have or as close to that as possible
--# this is slower
local foo = {}
for i=1,5 do
foo[i] = i
end
--#than this
local bar = {true,true,true,true,true}
for i=1,5 do
bar[i] = i
end
please note if you expect to use non numerical indexed values(or non sequential) then your pre indexed table should be done a bit differently
local foo = {[1] = true,[2] = true,[3] = true}
if you don't know the names of your non numerical indexes you can use the above example and name them 1-(size) and alternate removing an existing preset index (e.g. foo[5] = nil) and then adding a single new index
why?
lua tables are built off of arrays behind the scenes. a single lua table is built off of an array and a hash. both of these require a preset size and whenever a table increases in size both of these need to be rebuilt behind the scenes according to this new size. When you add a new numerical index in sequential order 1,2,3,4… this increases the size of the array. When you add a non sequential non numerical index e.g. ["foobar"] or [6] it increases the size of the hash. When setting a value in a table to nil the sizes wont decrease until after you set a new value into the table which is why the last example of presetting the removing a preset value before every added value still works for that speed increase.string metatables
string.len("foobar")--#slowest
("foobar"):len--#faster
local len = string.len
len("foobar")--#fastest
why?
the string metatable takes fewer/less intensive lookups than a global function but more than a local functionbuffer nested tables
foo.y.x = foo.y.x + 1 --#slower
local y = foo.y
y.x = y.x+1 --#faster
for anyone who wasnt aware making a new table from an old one only creates a pointer meaning changing y.x in our new table also changes foo.y.xwhy?
buffering a nested table means the machine has to do fewer lookups since it has to do 1 lookup per table in the nest every time you index a nested valueMAJOR: use internal buffers instead of iterators
essentially find workarounds that dont require you to use an iterator such as pairs(). iterators add a LOT of extra function overhead and slow down a program.why?
Most iterators in Computercraft Lua iterate through the entire table given every time regardless of the output. So if you have a table of 10 items and iterate through all of them it has to go through 100 items to return your 10use factorization
x*(y+z) --#faster
x*y + x*z --#slower
why?
lua cant factor on its own and a factored expression is just easier on the machine.Optimization
MAJOR: no variables in functions
local tbl = {"Hello","world!","I","am","me"}
function foo()
for i=1,#tbl do
print(tbl[i])
end
end
is inherently faster than if the table was placed within the function. This only applies to functions that will be used more than once. Usually an object building function for example would only be run once per program in which case there would be no noticeable speed differenceswhy?
every time a function with a variable defined in it is run that variable is recreated and added into the local registry for that function. Adding a variable to the registry takes a bit more time than simply looking up a local variable or upvalue.MAJOR: don't put constants in loops
Same use and reasoning as above in "don't use variables in functions". The difference in this one is it only applies for constants (variables the loop itself wont be changing) and doesn't have a catch 22 to it. Simply define your constants before the loop instead of inside itMAJOR:use table.concat to concat strings
please note this tip is only major if your program uses a lot of string concats ".." or you concat vary large strings. otherwise pretty self explanatorywhy?
in lua strings are immutable while tables are not. This means every time you concat a string the machine must iterate through every character in the two strings and create a while new string in the registry. When you concat a table the values are simply appended together to return the new instance. Appending is faster because the size of the first string doesn't need to be taken into account when determining the time it takes to finish while in a string concat the size of the first string is still taken into account because it has to be remade along with the rest of the strings. On top of this a string concat has to make a new string per concat in the series which is where the major speed advantage comes from
print("H".."e".."l".."l".."o".."!")--#creates 10 new strings to print Hello!
--[[#if you are confused
#"H" + "e" = 2
#"He" + "l" = 4
#"Hel" + "l" = 6
#"Hell" + "o" = 8
#"Hello" + "!" = 10
#]]
print(table.concat({"H","e","l","l","o","!"},""))--#creates 1 new string to print Hello!
use sequential numeric indexes in table
use 1,2,3,4,5… in your table indexes instead of ["foo"] or ["bar"] when possiblewhy?
array lookups are a small bit faster than hash lookupsDon't use math.max or math.min
instead of max or min use
x = foo>bar and foo or bar --#max
x = foo<bar and foo or bar --#min
why?
max and min both add extra function overhead that are not neededuse or when handling nil evaluation
there is a slight speed gain in putting nil checks in an or statement
if not foo then bar = "empty" else bar = foo end--#slower
bar = foo or "empty"--#faster
why?
if statements have a little bit more overhead when evaluating a nil value compared to or.Micro-optimization
WEAK:avoid upvalues
while still faster than global variables an upvalue (or enclosed local variable) is a small bit slower than a local variable an example of an upvalue below
local foo = 6
local bar = 5
function getfunction()
return function(name)
return foo+bar
end
end
this can be a catch 22. You should avoid creating functions that use any type of variable if possible (usually isn't). If you instead move a local variable into a function this will cause a decrease in speed larger than using the upvalue. this is explained in the next tip belowwhy?
simply put an upvalue merely references to the existing local variable instead of creating a new one. This gives the machine one extra step in order to find a value but this step runs very fast so the difference isn't muchmultiply dont divide
x*.5--#faster
x/2--#slower
why?
the lua VM is better at multiplication than divisionput all non variable math first
(1+2*3)+x --#faster
x+1+2*3 --#slower
why?
lua has to look up the value for a variable every time it does a math operation to that variable then sets the value and continues to the next operation in the series. setting the variable to the last item handled or as far back as it can be means fewer lookups and sets.avoid loadstring
local foo = loadstring("return true")--#slower
local foo = function() return true end--#faster
self explanatorywhy?
loadstring triggers the lua compiler which is a more intensive effort than simply creating a new function.WEAK: use for instead of while
for loops are a very very very small bit faster than while loops. Probably not worth the loss in readability if the program didn't need a for statement.why?
the virtual machine for lua has very specific instructions for for statements. The way these were written happen to be just slightly more micro-optimized than the instructions for a while statementavoid table.insert
foo[#foo+1] = 5--#faster
table.insert(foo,5)--#slower
[namedspoiler="why?]table.insert adds the extra overhead of a function slowing down the program.
[/namedspoiler]
[/namedspoiler]
[namedspoiler="avoid using the assert function"]
this function is more intensive than simple if statement