- Lua != Luau. We are using a language with many more optimizations than you can really say and give backing for. We have brand new VM opcodes that take care of performance for us most of the time,
FASTCALL
opcodes, the GETIMPORT
opcode and the DUPCLOSURE
op code, to name a few.
The second link leads to a tutorial written in 2014. We were using Lua 5.1 back then, not Luau, so the previous point stands.
As for the third link, the author’s implementation of OOP prevents DUPCLOSURE
from doing proper work, as they’re taking self
as an upvalue.
Here is a correct sample for you:
local lib = {}
function lib.new()
local self = {}
function self.hello(self) -- nups: 0
print"hi"
warn("this is self", self) -- self is correctly passed as an Argument, and not captured as an upreference.
end
return self
end
The following implementation allows DUPCLOSURE
to reduce memory usage because the function has no ‘unique’ up-references. You can still have up-references, they just have to be non-unique (i.e., a RemoteEvent defined at the beginning of the file, for example.).
As for the implementation showcased in the tutorial:
local lib = {}
function lib.new()
local self = {}
function self.hello() -- nups: 1
-- Takes self as an up-reference, this up-reference is unique.
-- This prevents Closure duplication, so the compiler emits the NEWCLOSURE opcode instead.
-- There are cases where DUPCLOSURE cannot duplicate, in which case it will fallback to making a new closure anyway. So no performance is lost, only gained if you do the proper implementation.
warn("this is self", self)
end
return self
end
Does not properly use the DUPCLOSURE
instruction. self
is unique for every time you call lib.new()
, because of it the closure is reallocated, meaning this is bad basically.
tl;dr: It is very likely scalable with the optimizations present on the VM. We have many opcodes doing the heavy lifting for us, and if you properly write your code, you can reduce memory usage even more, without metatables.
This, of course, does not change the fact you could perhaps save more memory using metatables and trying to maximize your ‘fast’ paths on the Luau VM.
Regardless, I still stand by my word. OP is making a simple module; adding metatables is unnecessary, especially when all the described issues are just rooted in
- Tutorials from another flavor of Lua being used (Lua 5.1 is NOT Luau)
- Tutorials being too old.
- Tutorials with an incorrect implementation of the alternative method being used.
If you do not believe me with these bytecode claims, fear not.
Go to → Luau Bytecode Explorer
Then enter the Luau code I have just presented, and after doing so, turn optimizations to 2
(which is what ROBLOX’s RCC now gives the client by default!).
Then you can turn off optimizations and compare both implementations. You will see that the opcode of DUPCLOSURE
(good) gets replaced with its other version NEWCLOSURE
Optimizations 2, Tutorial code
You can see in
anon_1
, which is
lib.new
, making use of
NEWCLOSURE
. This is because of what was previously said, as
self
is unique and it is taken as an up-reference, so the chance of duplicating the closure is inexistant.
Optimizations 2, proper implementation
As you can see now,
anon_1
, which is
lib.new
, now makes usage of
DUPCLOSURE
. This is because there are no unique up-preferences on the function. This allows it to not allocate new closures when it is creating objects, but reuse them.
Optimizations 2, proper implementation + an up-reference in the form of a Service
The previous is still true,
anon_1
is
lib.new
, and as you can see it makes usage of
DUPCLOSURE
. This one also shows that you
can take up-references, just not ones that are local to the function.
I would also like to note that in this case, the game
global is not marked as a mutable global. Mutable Globals? Let me explain. The Luau compiler will basically emit GETIMPORT
for every global present. This is not desirable for some cases, such as Workspace.DistributedGameTime
, in which the value changes every time step basically. That is why workspace
and game
are marked as mutable globals on the ROBLOX Luau compiler, which I sadly cannot make happen here without bringing out other methods, such as loading my studio executor, compiling bytecode with the proper compiler settings, and disassembling it using Konstant V2.1.
Explantion why GETIMPORT is not desired in the case described above
Why do we not want GETIMPORT
? The opcode will get the global and its values at load time. Basically, the value for Worskpace.DistributedGameTime
would be ‘frozen’ if GETIMPORT
is used
This is to make clear that, on ROBLOX, the bytecode generated by this luau code will not be the same, and instead of GETIMPORT
the GETGLOBAL
opcode would be used for accessing the game
global.
I hope this comes across with my point:
- We are no longer in Lua 5.1. Luau has many optimizations, and by simply saying ‘It increases memory linearly!!!’ taking the word of tutorials that are of other Lua flavors, old, or simply put make OOP wrong with this method is not correct.
Cheerio.