Does storing library functions in metamethod/modules instead of a table increase performance?

Hi, I’m currently building a library to use across most of the scripts in my games. I’m wondering if returning a table containing all the functions would cause a memory leak.

If it causes a memory leak, I could separate them into modules and optionally load them so that it won’t pass unnecessary functions.

I also thought of a way to optionally load them using the metamethod __index, but I’m not sure whether the metamethod itself and its functions would be passed as well, which could occupy even more memory.

I personally prefer the metamethod way if it doesn’t occupy more memory, as I can manage the code in a single view.

I’m estimating 5-10 functions for this library.

I don’t have much knowledge about memory allocations. Thanks!

The other main job of require is to avoid loading the same file twice. For that purpose, it keeps a table with the names of all loaded files. If a required file is already in the table, require simply returns.

from Programming in Lua : 8.1

it’s easy to check with repeated require:

local m = require(game.ReplicatedStorage.ModuleScript)
print(string.format("module %*", m))

local m = require(game.ReplicatedStorage.ModuleScript)
print(string.format("module %*", m))

prints the same number.

Speaking about module I’d recommend leaving metatables alone and use something like this:

--!strict

local function new(initialValue: number)
	
	-- private members
	local value = initialValue
	
	-- private methods
	local function incement()
		value += 1
	end
	
	return {
		-- public methods
		updateAndPrint = function()
			incement()
			print("value", value)
		end,
	}
end

return {
	new = new;
}

example usage:

local m = require(game.ReplicatedStorage.ModuleScript)

local i = m.new(10)
i.updateAndPrint()
i.updateAndPrint()

autocomplete works perfectly.

1 Like

Thanks! This summed it up.

I’m thinking that some functions kept in the table may not be used in some games.

In this case, can keeping functions in separate modules and then optionally requiring them make a meaningful difference? I’m wondering about the memory usage between separate module instances and functions defined in the table.

code by itself does not take much place.
Especially if compared to other resources like images or sounds.
So I would not bother.

1 Like

I remember somewhere I saw it is posssible for tables to use memory for every instance of this table, even if it appears same. I forgot what actions can lead to this tho, but theoretically it is possible for same module script to return different tables, allocating memory for each of them.

I might be wrong.

1 Like

son, you got rid of metatables to still have horrible version

could you please elaborate?..
I am always happy to learn something new.
thank you!

1 Like
export type ObjectType = {
    first: any,
    public_method: (self: ObjectType) -> (),
}

local function public_method(self: ObjectType)

end

--// simply don't export this and it stays private
local function private_method(self: ObjectType)
    
end

local function new(first: any): ObjectType
    local object = {
       first = first or "undefined",
       public_method = public_method,
    }

    return object
end

the public method is global, not created seperately for every OOP instance

Thanks for the answer!

To be completely correct I guess your sample should be:

type A = {
	value: number;
	updateAndPrint: (A) -> ();
}

local function incement(self: A)
	self.value += 1
end

local function updateAndPrint(self: A)
	incement(self)
	print("value", self.value)
end

local function newA(initialValue: number):  A

	return {
		value = initialValue;
		updateAndPrint = updateAndPrint;
	}
end

Yeah, I have been there, and I currently prefer closures more because:

  1. To access value from private methods it must be in the table. So it is not private any more:
    It’s possible to see it by printing the table. And modify it. To modify upvalues debug library is required (which is hard to do unintentionally).
  2. methods located outside of new’s scope are not private inside the same file. For example, If I add B and create it’s new in the same file, it can access A’s private increment, which is not always intended.
    With closures it’s possible to define as many constructors of different types in the same file without polluting “namespace” scope.
  3. If you need to change prototype of a member function: type definition must and implementation be changed.
    In case of closures only implementation, so it’s sort of less typing.
  4. Speaking about less typing closures does not require typing “self.” all around.
  5. also check text size of functionally the same code

Closures have drawbacks also:

  1. new method can become large, so and it can be not comfortable if you are “a method must fit one page” kind of person.
  2. slight memory overhead: each method captures upvalues.

So, let’s sum up closures:

  • encapsulation
  • less typing
  • more clean global scope

Thank you!

what is this AI response son :skull:

could you please elaborate on this?

what makes you feel it’s AI generated?

From my side:

  • i find that using AI on forums answering live persons is just embarrassing and not respectful
  • i have emphasized that i used to write the code in a way you have suggested as a better version
  • in my turn i have shared my points why i have switched of the suggested way to using closures instead

Thank you!