Is it better to declare a local inner or outer loop?

I use this:

do local a for i=1,1000000 do a = <some expression> <...> --do something with a end end 

instead

 for i=1,1000000 do local a = <some expression> <...> --do something with a end 

My reasoning is that creating a local variable is 1,000,000 times less efficient than creating it only once and reusing it at each iteration.

My question is: is this true or is there another technical detail that I am missing? I ask because I don’t see anyone doing this, but I’m not sure the reason is that the advantage is too small or because it is actually worse. For the better, I mean using less memory and faster execution.

+5
source share
4 answers

Like any performance issue, first measure. On a unix system, you can use time:

 time lua -e 'local a; for i=1,100000000 do a = i * 3 end' time lua -e 'for i=1,100000000 do local a = i * 3 end' 

output:

  real 0m2.320s user 0m2.315s sys 0m0.004s real 0m2.247s user 0m2.246s sys 0m0.000s 

In Lua, the more local version looks a bit slower because it does not initialize a zero. However, this is not a reason to use it, use the most local area, because it is more readable (this is a good style in all languages: see this question asked for C , Java and C # )

If you reuse a table instead of creating it in a loop, there will probably be a larger difference in performance. In any case, measure and approve readability when you can.

+9
source

I think there is some confusion about how compilers deal with variables. From the point of view of the high level of human life, it is natural to think about defining and destroying a variable in order to have some kind of “value” associated with it.

However, this is not necessary for the optimization compiler. The variables that you create in a high-level language are more like temporary “pens” in memory. The compiler looks at these variables, and then translates it into an intermediate representation (something closer to the machine) and calculates where to store everything, mainly for the purpose of allocating registers (the most direct form of memory for CPU use). Then it converts IR to machine code, where the idea of ​​a “variable” does not even exist, only a place to store data (registers, cache, drum, disk).

This process involves reusing the same registers for several variables, provided that they do not interfere with each other (provided that they are not needed at the same time: do not "live" at the same time).

In other words, with code like:

 local a = <some expression> 

The resulting assembly might look something like this:

 load gp_register, <result from expression> 

... or it may already have the result of some expression in the register, and the variable ends completely disappearing (just using the same register for this).

... which means there is no "cost" to the existence of the variable. It simply translates directly to the register, which is always available. There is no “cost" to "create a registry" because registers always exist.

When you start creating variables in a wider (less local) area, contrary to what you think, you can actually slow down the code. When you do this superficially, you struggle with the distribution of the compiler register and make it difficult for the compiler to figure out which registers to allocate for what. In this case, the compiler can spill more variables onto the stack, which is less efficient and actually has a cost. A smart compiler may still produce equally efficient code, but you could do something slower. Help with the compiler here often means more local variables used in small areas where you have a better chance for efficiency.

In assembly code, reusing the same registers whenever possible is effective to avoid stack leaks. In high-level languages ​​with variables, this is similar to the opposite. Reducing the volume of variables helps the compiler figure out which registers it can reuse, because using a more local area for variables helps tell the compiler which variables do not live at the same time.

Now there are exceptions when you start to include user-defined constructor and destructor logic in languages ​​such as C ++, where reusing an object can prevent over construction and destruction of an object that can be reused. But this does not apply in a language such as Lua, where all the variables are basically plain old data (or it processes data collected through garbage or user data).

The only time you can see the improvement using less local variables is that it somehow reduces the garbage collector. But this will not happen if you just reinstall the same variable. To do this, you will have to reuse entire tables or user data (without re-binding). In other words, reusing the same table fields without re-creating a whole new one can help in some cases, but reusing the variable used to refer to the table is unlikely to help and may actually interfere.

+5
source

All local variables are "created" at the time of compilation ( load ) and are simply indices in the block of locations of records of function activation. Each time you define local , this block is generated by 1. Each time the do..end / lexical block is completed, it is compressed. The peak value is used as the total size:

 function () local a -- current:1, peak:1 do local x -- current:2, peak:2 local y -- current:3, peak:3 end -- current:1, peak:3 do local z -- current:2, peak:3 end end 

The above function has 3 local slots (defined in load , not at runtime).

Regarding your case, there is no difference in the size of the block of local residents, and, in addition, luac /5.1 generates equal lists (only indexes change):

 $ luac -l - local a; for i=1,100000000 do a = i * 3 end ^D main <stdin:0,0> (7 instructions, 28 bytes at 0x7fee6b600000) 0+ params, 5 slots, 0 upvalues, 5 locals, 3 constants, 0 functions 1 [1] LOADK 1 -1 ; 1 2 [1] LOADK 2 -2 ; 100000000 3 [1] LOADK 3 -1 ; 1 4 [1] FORPREP 1 1 ; to 6 5 [1] MUL 0 4 -3 ; - 3 // [0] is a 6 [1] FORLOOP 1 -2 ; to 5 7 [1] RETURN 0 1 

vs

 $ luac -l - for i=1,100000000 do local a = i * 3 end ^D main <stdin:0,0> (7 instructions, 28 bytes at 0x7f8302d00020) 0+ params, 5 slots, 0 upvalues, 5 locals, 3 constants, 0 functions 1 [1] LOADK 0 -1 ; 1 2 [1] LOADK 1 -2 ; 100000000 3 [1] LOADK 2 -1 ; 1 4 [1] FORPREP 0 1 ; to 6 5 [1] MUL 4 3 -3 ; - 3 // [4] is a 6 [1] FORLOOP 0 -2 ; to 5 7 [1] RETURN 0 1 

// [n] comments are mine.

+3
source

Note: defining a variable inside a loop ensures that after one iteration of this loop, the next iteration will not be able to use the same stored variable again. Defining it before the for loop allows you to transfer the variable through several iterations, like any other variable that is not defined in the loop.

Further, to answer your question: Yes, it is less efficient because it re-initiates the variable. If the Lua JIT- / Compiler has good pattern recognition, it may just reset the variable, but I cannot confirm this and not deny it.

+2
source

All Articles