Basic facts
Before running any code, Lua translates (precompiles) the source into an internal format.
This format is a sequence of instructions for a virtual machine, similar to machine code for a real CPU. This internal format is then interpreted by C code that is essentially a while loop with a large switch inside, one case for each instruction.
Perhaps you have already read somewhere that, since version 5.0, Lua uses a register-based virtual machine. The “registers” of this virtual machine do not correspond to real registers in the CPU, because this correspondence would be not portable and quite limited in the number of registers available. Instead, Lua uses a stack (implemented as an array plus some indices) to accommodate its registers. Each active function has an activation record, which is a stack slice wherein the function stores its registers. So, each function has its own registers. Each function may use up to 250 registers, because each instruction has only 8 bits to refer to a register.
About tables
Usually, you do not need to know anything about how Lua implement tables to use them. Actually, Lua goes to great lengths to make sure that implementation details do not surface to the user. However, these details show themselves through the performance of table operations. So, to optimize programs that use tables (that is, practically any Lua program), it is good to know a little about how Lua implements tables.
The implementation of tables in Lua involves some clever algorithms. Every table in Lua has two parts: the array part and the hash part. The array part stores entries with integer keys in the range 1 to n, for some particular n. (We will discuss how this n is computed in a moment.) All other entries (including integer keys outside that range) go to the hash part.
About strings
As with tables, it is good to know how Lua implements strings to use them more efficiently.
The way Lua implements strings differs in two important ways from what is done in most other scripting languages. First, all strings in Lua are internalized; this means that Lua keeps a single copy of any string. Whenever a new string appears, Lua checks whether it already has a copy of that string and, if so, reuses that copy. Internalization makes operations like string comparison and table indexing very fast, but it slows down string creation.
Second, variables in Lua never hold strings, but only references to them.
This implementation speeds up several string manipulations. For instance, in Perl, when you write something like $x = $y, where $y contains a string, the assignment copies the string contents from the $y buffer into the $x buffer. If the string is long, this becomes an expensive operation. In Lua, this assignment involves only copying a pointer to the actual string.
Reduce, reuse, recycle
When dealing with Lua resources, we should apply the same three R’s promoted for the Earth’s resources.
Reduce is the simplest alternative. There are several ways to avoid the need for new objects. For instance, if your program uses too many tables, you may consider a change in its data representation. As a simple example, consider that your program manipulates polylines. The most natural representation for a polyline in Lua is as a list of points, like this:
polyline = { { x = 10.3, y = 98.5 }, { x = 10.3, y = 18.3 }, { x = 15.0, y = 98.5 }, ... }
Although natural, this representation is not very economic for large polylines, as it needs a table for each single point. A first alternative is to change the records into arrays, which use less memory:
polyline = { { 10.3, 98.5 }, { 10.3, 18.3 }, { 15.0, 98.5 }, ... } function foo (...) for i = 1, n do local t = {1, 2, 3, "hi"} -- do something without changing ’t’ ... end end local t = {1, 2, 3, "hi"} -- create ’t’ once and for all function foo (...) for i = 1, n do -- do something without changing ’t’ ... end end
The same trick may be used for closures, as long as you do not move them out of the scope of the variables they need. For instance, consider the following function:
function changenumbers (limit, delta) for line in io.lines() do line = string.gsub(line, "%d+", function (num) num = tonumber(num) if num >= limit then return tostring(num + delta) end -- else return nothing, keeping the original number end) io.write(line, "\n") end end We can avoid the creation of a new closure for each line by moving the inner function outside the loop: function changenumbers (limit, delta) local function aux (num) num = tonumber(num) if num >= limit then return tostring(num + delta) end end for line in io.lines() do line = string.gsub(line, "%d+", aux) io.write(line, "\n") end end
….
Télécharger cours: Lua Performance Tips (204 KO) (Cours PDF)