Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Constants in Lua?

edited January 2013 in General Posts: 398

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.

Any thoughts? :-)

Comments

  • edited January 2013 Posts: 500

    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

    Hope this helps.

  • Posts: 398

    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. :-)

  • edited January 2013 Posts: 500

    mhh.. I think there is no. You've done it already the right way ;P

  • SimeonSimeon Admin Mod
    Posts: 4,986

    .@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

  • Posts: 398

    Thanks chaps - from what you've said, seems I'm already doing it the right way. :-)

  • Jmv38Jmv38 Mod
    Posts: 3,295

    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
  • Posts: 398

    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..

  • Jmv38Jmv38 Mod
    edited January 2013 Posts: 3,295

    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.

  • edited January 2013 Posts: 489

    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.

  • edited January 2013 Posts: 557

    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?

  • Jmv38Jmv38 Mod
    Posts: 3,295

    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.

  • Posts: 398

    @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. :-)

  • SimeonSimeon Admin Mod
    Posts: 4,986

    .@andymac3d Have you thought about @jmv38's third solution? That is, local variables declared in "tab" scope. For example:

    local SomeConstant = 5
     
    function setup() end
     
    function draw() end
  • Posts: 500

    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).

  • edited January 2013 Posts: 398

    Thanks @Simeon & @se24vad - So to clarify, it appears that:

    Variables defined within a Tab are Global by default.

    Local Variables defined within a Tab are Local to that Tab.

    Locals defined within functions within a Tab are local to that function Only.

    Does this make sense?

  • edited January 2013 Posts: 500

    correctly! ;)

  • Posts: 398

    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.

    Any thoughts?

  • Posts: 563

    .@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.

  • edited January 2013 Posts: 398

    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! ;-)

  • Posts: 563

    .@andymac3d - you probably shouldn't use _G explicitly, sorry I should have been clearer, use your own table. Any global ends up in _G.

    You can have a look at what is in there using:

    for k, v in pairs(_G) do print(k, v) end
    
Sign In or Register to comment.