This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.
Programming in Lua | ||
Part II. Tables and Objects Chapter 17. Weak Tables |
A common programming technique is to trade space for time. You can speed up some functions by memoizing their results so that, later, when you call the function with the same arguments, it can reuse the result.
Imagine a generic server that receives
requests containing strings with Lua code.
Each time it gets a request,
it runs loadstring
on the string,
and then calls the resulting function.
However, loadstring
is an expensive function
and some commands to the server may be quite frequent.
Instead of calling loadstring
over and over
each time it receives a common command like "closeconnection()"
,
the server can memoize the results from loadstring
using an auxiliary table.
Before calling loadstring
,
the server checks in the table whether that string
already has a translation.
If it cannot find the string,
then (and only then) the server calls loadstring
and stores the result into the table.
We can pack this behavior in a new function:
local results = {} function mem_loadstring (s) if results[s] then -- result available? return results[s] -- reuse it else local res = loadstring(s) -- compute new result results[s] = res -- save for later reuse return res end end
The savings with this scheme can be huge.
However, it may also cause unsuspected wastes.
Although some commands repeat over and over,
many other commands happen only once.
Gradually,
the table results
accumulates
all commands the server has ever received
plus their respective codes;
after enough time, this will exhaust the server's memory.
A weak table provides a simple solution to this problem.
If the results
table has weak values,
each garbage-collection cycle will remove
all translations not in use at that moment
(which means virtually all of them):
local results = {} setmetatable(results, {__mode = "v"}) -- make values weak function mem_loadstring (s) ... -- as beforeActually, because the indices are always strings, we can make that table fully weak, if we want:
setmetatable(results, {__mode = "kv"})The net result is exactly the same.
The memoize technique is also useful to ensure the uniqueness of
some kind of object.
For instance, assume a system that represents colors as tables,
with fields red
, green
, and blue
in some range.
A naive color factory generates a new color for each new request:
function createRGB (r, g, b) return {red = r, green = g, blue = b} endUsing the memoize technique, we can reuse the same table for the same color. To create a unique key for each color, we simply concatenate the color indices with a separator in between:
local results = {} setmetatable(results, {__mode = "v"}) -- make values weak function createRGB (r, g, b) local key = r .. "-" .. g .. "-" .. b if results[key] then return results[key] else local newcolor = {red = r, green = g, blue = b} results[key] = newcolor return newcolor end endAn interesting consequence of this implementation is that the user can compare colors using the primitive equality operator, because two coexistent equal colors are always represented by the same table. Note that the same color may be represented by different tables at different times, because from time to time a garbage-collector cycle clears the
results
table.
However, as long as a given color is in use,
it is not removed from results
.
So, whenever a color survives long enough to be compared with a new one,
its representation also survives long
enough to be reused by the new color.
Copyright © 2003–2004 Roberto Ierusalimschy. All rights reserved. |