I’m trying to eliminate all the ‘magic numbers’ in my code in respect to a game I’m developing and turn them into constants that have:
a) global scope b) are fast to access, as speed is an issue.
Obviously, I can define them in my ‘setup’ as a set of Global variables that would fulfil criteria a), but for optimisation purposes I really don’t want the performance overhead of using them. As a ‘true’ set of constants, these wont be modified, just read.
I’m scratching my head a bit to work out if there’s an better way of doing this in Lua - as this is achievable in other languages. Codea clearly supports these sorts of ‘immutable’ constants e.g. WIDTH, HEIGHT etc… Just curious the best way of defining my own custom ones.
First to know, you can define global and local (scoped) variables everywhere, not just inside the setup function. Second, if you want to retain performance, try to use local variables whenever you can, because they get garbage-collected, in opposite to global ones (except they get nil-ed out). At least, this is my understanding of the whole thing.
Define your constants just like you would normally do: VARNAME = value. Whereas the locals are defined as: local VARNAME = value
Yes, I’m aware of local variables (and functions!) and I’ve used these throughout - but there must be a better way of creating immutable constants that are fast to access with global scope. But thanks anyway.
.@andymac3d global variables are probably the way to go. Though keep in mind that global variables are slower to access than local variables. So, for example:
MyConstantVar = 3.0
function test()
local x = MyConstantVar
print(MyConstantVar)
print(x) -- This statement is faster than the one above
end
```
So if you are going to make use of a particular constant a lot within a function, then it is best to store it in a local.
This is because local variables are simply accessed by register index in the interpreter — basically an array lookup. Global variables are accessed by hash table lookup.
There's more info here: http://lua-users.org/wiki/OptimisingUsingLocalVariables
I had exactly the same question as you, so i looked on the web and made some tests. If you run the following code you’ll see that
1- access to a local variable is fastest : 400kOp/frame.
2- access to a global variable is slower: 100kOp/frame.
3- an ineresting intermediate solution is a constant defined as local but outside the function, in the same tab : 250kOp/frame.
The problem of (1) is that i have 30 functions in each of my tabs. I dont want to copy my 25 constants in each of these 30 function! But it is ok to copy it once per tab. So (3) is not so bad compared to (1), much better than (2) and not too complex to maintain => i chose this option
--# Main
-- test local
-- Use this function to perform your initial setup
function setup()
local v,t0,N = 0,0,1000000
local obj = Object()
print("test speed and local")
globalValue = 1
-- measure offset
local t0=os.clock()
obj:calcul6(N)
local dt0=os.clock()-t0
-- self
local t2=os.clock()
obj:calcul10(N)
local dt2=os.clock() - t2 - dt0
info(dt2,N,"self.value inside function")
-- measure local
local t1=os.clock()
obj:calcul7(N)
local dt1=os.clock()-t1-dt0
info(dt1,N,"local value outside function")
-- measure global
local t2=os.clock()
obj:calcul8(N)
local dt2=os.clock() - t2 - dt0
info(dt2,N,"global value outside function")
-- simeon trick
local t2=os.clock()
obj:calcul9(N)
local dt2=os.clock() - t2 - dt0
info(dt2,N,"local = global inside function")
end
function info(dt,N,txt)
print(txt.." : " .. math.floor(N/dt*1/60/1000) .." kOp/frame")
end
function draw()
end
--# Object
Object = class()
function Object:init(x)
self.value = 1
end
function Object:calcul6(N)
local a
for i=1,N do end
end
globalValue =1
local localValue = 1
function Object:calcul7(N)
local a
for i=1,N do a = localValue end
end
function Object:calcul8(N)
local a
for i=1,N do a = globalValue end
end
function Object:calcul9(N)
local x = globalValue
local a
for i=1,N do a = x end
end
function Object:calcul10(N)
local a
for i=1,N do a = self.value end
end
I’m a big fan of local variables, but I guess your code can quickly get verbose if you’ve lots of constants represented as globals that are then copied as locals within a function - a small price I guess for a performance gain.
@Jmv38, thanks for testing these options so comprehensively - although I’m still a bit unclear regarding the scoping rules for local variables within ‘tabs’ . To paraphrase, you said “Option 3. defined as Local outside the Function in the same Tab”. Unless I’ve misunderstood, surely the scope of your local variable in this case is within your ‘Object’ class not necessarily the tab itself which kind of makes more sense.
I certainly think it makes more sense to go down this route for readability etc…
Actually with solution (3) my understanding is that:
the variable is accessible in the lines after it is declared, not before. So it is not even in the Object scope, only in the functions declared after the variable.
my understanding is that it is not accessible to other tabs, but maybe it is again different for tabs after and before, in the order of apparition from left to right. To be checked.
I believe that code in each Codea ‘tab’ is a Lua ‘chunk’ (see here) and that, consequently, local external variables in a tab have a scope limited to the tab and the functions within that tab.
This here is way way way kludgey, but couldn’t one define a function along the lines of:
function getConstant(name)
if name == "EYES" then
return 2
elseif name == "HAIR" then
return "brown"
elseif name == "ENAMEL" then
return "insufficient"
...etc...
end
```
Then use as needed:
function findProbableNumberOfEyes(numberOfPeople)
local probableEyes = numberOfPeople * getConstant(EYES)
return probableEyes
end
```
That would store a consistent set of values without making them globals, right? Would that keep the speed benefit of using locals?
Hello @ubergoober. I think your code would be extremely long: function call, many tests… Probably 30x slower than the best local definition. Just calling a function i drop to 15 kOp/frame.
@Jmv38, I agree - this is fairly long winded, especially if you have lots of constants and can probably be represented better as a table if this was an option.
Back to the subject of global/local scope for variables - @mpilgrem, after some tests it appears that the scope of a variable declared in a tab is global, rather than local to the tab.
I still think there must be a better way of defining ‘immutable’ constants that have a fast lookup - i’ll go with the global → local route as a compromise for now, but i’ll look into this some more.
I think of tabs also as lua chunks. Actually you can define all classes inside the Main tab and it would still work as expected. Which leads to the assumption, that local variables inside a tab, are local to that code chunk (class).
Hmmmmm… Had a bit of time to look at @Jmv38 s benchmark code regarding the relative performance of various local/global/self.value access times of variables. Whilst its pretty obvious that locals would outperform globals, I was a bit of a surprise how much slower a simple self.value reference takes ie. marginally faster than a global variable on my iPad.
As I understand it (via the Wiki), internally ‘Classes’ are implemented in Codea with meta tables and use local variables extensively - so it’s therefore interesting why the benchmark is relatively slow.
.@andymac3d - To extend your summary slightly, if you define a local variable within a function, you can only access it from within that function. However, if a function is enclosed in another function, then it has access to all the local variables of that function. In these circumstances, the external local variable is called an “upvalue”.
Another way to do constants would be to use a table as a name space. The table is defined within the scope that you need to access the constants. Lua does this with its _G environment table. This doesn’t help with the speed issue but it keeps your constants from conflicting with those already defined in Codea.
Thanks @Reefwing, I’ll have a look at the table/namespace/_G env table as an option. Good to know about Luas ‘upvalues’ - I think I discovered this ‘feature’ by accident when some of my local scoping went awry!