Introduction
Memory usage is a discussion topic that comes up time and again. Some ways to represent data, as we know, are more efficient than other ways. This post is a catalogue of common object structures, and their respective byte-counts.
Each snippet listed will be executed with the following code block; the comment {{ Snippet of code. }}
will be replaced by said snippet. The output values will be labelled and placed following the included code.
c = 0
c = collectgarbage('count')
do
--{{ Snippet of code. }}
--L1: Differential caused by executed code.
print(1024 * (collectgarbage('count') - c), ' bytes.')
end
--L2: Differential that does not get disposed, after one second.
wait(1) print(1024 * (collectgarbage('count') - c), ' bytes.')
--L3: Differential that does not get disposed, after two seconds.
wait(1) print(1024 * (collectgarbage('count') - c), ' bytes.')
Basic Value Types*
Value types should only exist in their specified scope. Therefore, they don’t affect the garbage-collection count.
local n = 0
L1: 0 bytes.
L2: 0 bytes.
L3: 0 bytes.
local b = false
L1: 0 bytes.
L2: 0 bytes.
L3: 0 bytes.
local s = '666'
L1: 0 bytes.
L2: 0 bytes.
L3: 0 bytes.
Tables (array)
Empty tables are 64 bytes; each array entry takes up 16 bytes more, plus the counted size of the data item itself.
local t = {}
L1: 64 bytes.
L2: 0 bytes.
L3: 0 bytes.
local t = { 0 }
L1: 80 bytes. (64 + 16)
L2: 0 bytes.
L3: 0 bytes.
local t = { 0, 1 }
L1: 96 bytes. (64 + 16 + 16)
L2: 0 bytes.
L3: 0 bytes.
local t = { {} }
L1: 144 bytes. (64 + 16 + 64)
L2: 0 bytes.
L3: 0 bytes.
Tables (dictionary)
Empty tables are 64 bytes; each dictionary entry takes up 40 bytes more, plus the counted size of the index/value data itself.
local t = { [666] = true }
L1: 104 bytes. (64 + 40)
L2: 0 bytes.
L3: 0 bytes.
local t = { [666] = {} }
L1: 168 bytes. (64 + 40 + 64)
L2: 0 bytes.
L3: 0 bytes.
local t = { [ {} ] = {} }
L1: 232 bytes. (64 + 40 + 64 + 64)
L2: 0 bytes.
L3: 0 bytes.
Userdata Objects
userdata
objects are the ones you see on Explorer (i.e. Part
, Workspace
), though you can also create your own using newproxy
. Note that the former (Instance
) take longer to dispose than other object types. They take up 40 and 24 bytes, respectively.
local p = Instance.new( 'Part' )
L1: 40 bytes.
L2: 40 bytes.
L3: 0 bytes.
local p = newproxy()
L1: 24 bytes.
L2: 0 bytes.
L3: 0 bytes.
Functions
Functions count up to 40 bytes on the garbage-collector. Since they aren’t transferable through RemoteEvent
s or RemoteFunction
s, you don’t have to concern yourself with that so much. All of the following snippets are compilable. Note that these functions are never called.
local f = function() end
L1: 40 bytes.
L2: 0 bytes.
L3: 0 bytes.
local f = function() undefinedFunction() end
L1: 40 bytes.
L2: 0 bytes.
L3: 0 bytes.
local f = function() return function() end end
L1: 40 bytes.
L2: 0 bytes.
L3: 0 bytes.
*All numbers are doubles, so they take up 8 bytes of memory.
*The memory makeup of Boolean values is not explained, so let’s assume the VM allocates exactly 1 byte for them.
*As Lua is derived from C/C++, strings may be assumed to take up 1 byte per character, with special UTF-8 characters taking up 2 bytes. It is possible for the VM to allocate a few more bytes to ensure type safety.
*nil (null) is a glorified pointer to … nothing; make the wild assumption that it takes up no space on the VM.