[ADVANCED] Amount of G.C. Memory Taken up by Common Object Types

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 RemoteEvents or RemoteFunctions, 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.

36 Likes

This topic was automatically closed after 1 minute. New replies are no longer allowed.