In my experience, with LuaJIT, the only general “trick” that makes a difference in performance is “localizing” functions and other values to avoid repeated table accesses, for things that you are using repeatedly.
For example if your loop used a math function, set it to a local variable first, or if you’re accessing the same property or sub-property of a table in a loop, put it in a local variable first instead of accessing it repeatedly
local max = math.max
local function doSomething()
for i=1,1000 do
local v = max(...)
end
end
local function doSomethingWithObj(obj)
-- Localize here instead of accessing repeatedly inside the loop.
local prop = obj.thing.property
for i=1,1000 do
-- Do something with `prop`...
end
end
There are also some differences in cost between various built-in functions that may not be obvious of course, though unless you’re using them thousands of times it won’t matter. Otherwise, LuaJIT does all the optimization for you and makes abstractions free.
Regular Lua is much slower than LuaJIT as baseline, and also does not get the “no-cost abstraction” that LuaJIT gives you, but you would have to test specific code against alternatives to know what’s costly and what’s not.