WoW:Memory usage

Revision as of 06:29, 9 August 2007 by WoWWiki>Kirkburn (A little tidying)

Information on the memory usage of data in the WoW Lua environment.

Data

There are two basic classes of data that can be assigned to variables, passed around, etc, in LUA.

Values

Simple data types like numbers and booleans are represented as their actual values, and are passed around as such.

References

Complex data types (Userdata, Strings, and Tables) are represented as references to their actual value. Each reference fits in the same amount of memory as a value, but then the actual object that is referenced takes up additional space.

Strings

The contents of strings are immutable, so once created a string cannot be changed, just replaced with another string. This allows LUA to maintain a pool of every distinct string value in use, such that multiple occurrences of the same string are all in fact references to the same unchangeable string object.

If you have a table containing 30,000 entries of "Visit http://www.wowwiki.com/ to find out how to get more out of the World of Warcraft", there's actually only one instance of the text itself. Each occurrence of the string is a reference to that shared instance.

Tables

Unlike strings, tables can be altered, and may grow as entries are added to them. A table is made up of 3 parts:

  • The table itself - 36 bytes
  • 'List' entries - Those with consecutive integer indexes starting at 1 - 16 bytes each
  • 'Map' entries - Those with non-consecutive integer indexes, and string indexes.- 40 bytes each

An empty table is just the minimum 32 bytes. As you start adding entries then it will begin to accumulate memory in which to store those. Memory accumulation isn't linear however, instead there is an exponental algorithm, so that as the table gets larger, incrementally larger numbers of elements are pre-allocated.

For example, the size of a list of numbers increases as follows as new values are inserted: 36, 52, 68, 100, 100, 164, 164, 164, 164, etc..

Tables which mix both forms of values (list entries, and map entries) use a mix of the two entry types

In lua 5.1 tables that are created with literal initializers such as x = { "a", "b", key = "value" } allocate exactly what they need without any extra pre-allocation.

Closures

Closures are the manifestation of function prototypes within the lua environment. Lua closures consist of a base object, with some storage for each upvalue the closure references, and then there's allocated space for each upvalue itself. In WoW 2.2.0 (Measured on the PTR on 2007-08-08) the memory usage for each is as follows:

  • 28 bytes for the base closure
  • 4 bytes for each upvalue it references
  • 32 bytes for each distinct upvalue value

This means that an arrangement like...

function createClosures(a,b,c)
  return function() return 1,a,b,c end,
         function() return 2,a,b end
end

...allocates 172 bytes of memory every time it is run (28 bytes for each closure (56), 4 bytes for each of the upvalue references, three from the first closure, and two from the second (20), and 32 bytes for each of the three distinct upvalues (96))

Frames

Since a bulk of a frame's existance is on the C++ side of things, it's a bit difficult to get a true sense of their memory impact, however it's possible to measure the lua footprint of the frame alone. It turns out that a frame's lua footprint is initially just the size of its table representation (76 bytes, in WoW 2.2), there doesn't appear to be any other "magic" internal repository of other data.

Having said that, it's a little tricky to fully test this because many of lua's internal structures (The global environment, the lua string table, the internal C library reference table) are dynamically managed and grow with the exponential algorithm mentioned earlier, so it's possible that there's some table entries there being consumed by the frames.

Most puzzling (and the largest reason I suspect there is a hidden internal data structure somewhere) is that there doesn't appear to be a readily measurable impact of :SetScript on a frame, yet the frame seems to manage to keep a reference to its handler quite happily. I'm guessing that each non-nil script handler really consumes about 16 bytes in the form of an entry in the library's reference table, but I could be entirely wrong! - Flickering 06:10, 9 August 2007 (UTC)

Local Variables

Locally scoped variables in functions are stored on the stack, rather than being part of the garbage collected memory heap, however if the variable contains a reference type, then the actual data referenced is not on the stack. (Though it's not actually a stack, per se, see the LUA source code for details)

Reference Functions

In WoW 2.2, based on the PTR on 2007-08-08, the following functions can be used to determine the size of a dynamically allocated hash or array table:

function DynamicHashTableSize(entries)
  if (entries == 0) then
    return 36;
  else
    return math.pow(2, math.ceil(math.log(entries) / math.log(2))) * 40 + 36;
  end
end
function DynamicArrayTableBytes(entries)
  return 36 + math.pow(2, math.ceil(math.log(entries) / math.log(2))) * 16
end