Jump to content

Memory Cost of Local Functions


Recommended Posts

So if a function of a prefab post-init is declared locally like this:

AddPrefabPostInit(prefab, function(inst)
	local function fn() end
	inst.fn = fn
end)

Would there be as many fn's saved as there are instances of the prefab, since the function is declared locally, and each new instance would see the function redeclared? How much worse would the above example be, than, say, declaring the function outside of the prefab post init:

local function fn() end
AddPrefabPostInit(prefab, function(inst)
	inst.fn = fn
end)

Since the common way to overwrite a function is to frame it inside another function:

AddPrefabPostInit(prefab, function(inst)
	local old = inst.fn 
	inst.fn = function(...)
--		overwrites
		return old(...)
	end 
end)

And usually this framing is done locally at the level of the instances, is this a major cause for the lags of mods?

And would something like this be substantially better for performance, or would it just unnecessarily complicate the mod?

local function overwrite(postinitfn, class, tablefn, name, func)
	local old
	local new 
		
	postinitfn(class, function(self)
		if old == nil then 
			old = tablefn(self)[name]
-- tablefn is a function that locates the function to overwrite, like tablefn = function(inst) return inst.components.combat end, while name = "CanTarget"
			if old == nil then 
				old = false 
			end 
			
			new = function(...)
				func(...)
				if type(old) == "function" then 
					return old(...)
				end 
			end 
		end 
		
		tablefn(self)[name] = new 
	end)
end)

 

Edited by Bad Willow
Link to comment
Share on other sites

38 minutes ago, Bad Willow said:

So if a function of a prefab post-init is declared locally like this:


AddPrefabPostInit(prefab, function(inst)
	local function fn() end
	inst.fn = fn
end)

Would there be as many fn's saved as there are instances of the prefab, since the function is declared locally, and each new instance would see the function redeclared? How much worse would the above example be, than, say, declaring the function outside of the prefab post init:


local function fn() end
AddPrefabPostInit(prefab, function(inst)
	inst.fn = fn
end)

 

Yes, each prefab would get its own unique function stored to it in this case.  Worse is a relative term.  If your code doesn't require the unique instancing of the function and it's okay for it to be shared (generic function), then it would be better in terms of memory usage to pull it out and make it the same function used for every instance like what you did with the second block.

 

40 minutes ago, Bad Willow said:

Since the common way to overwrite a function is to frame it inside another function:


AddPrefabPostInit(prefab, function(inst)
	local old = inst.fn 
	inst.fn = function(...)
--		overwrites
		return old(...)
	end 
end)

And usually this framing is done locally at the level of the instances, is this a major cause for the lags of mods?

You'd only experience runtime hiccups relative to memory if the computer is full and the OS starts paging it to disk.  When you invoke a function in LUA it does a series of checks to try to find out what the variable means, first by local scope and cascades up scoping until it hits _G to then look for that.  At each level of scope it's a hashmap lookup (as far as I recall) to see if it can't find what it is.  This is why people tend to local scope some things before doing tight runloops in LUA- it speeds it up in cache.

Mostly mods create performance issues due to poor handling of assets (giant atlases), an overuse of TheSim:FindEntities, very low delay timers everywhere, and mass entity creation.

Function hooking is a small overhead that's unavoidable for mods to work with each other nicely.  There are other methods done like a more robust event chain system, but they all have their pros and cons for each.

 

48 minutes ago, Bad Willow said:

And would something like this be substantially better for performance, or would it just unnecessarily complicate the mod?


local function overwrite(postinitfn, class, tablefn, name, func)
	local old
	local new 
		
	postinitfn(class, function(self)
		if old == nil then 
			old = tablefn(self)[name]
-- tablefn is a function that locates the function to overwrite, like tablefn = function(inst) return inst.components.combat end, while name = "CanTarget"
			if old == nil then 
				old = false 
			end 
			
			new = function(...)
				func(...)
				if type(old) == "function" then 
					return old(...)
				end 
			end 
		end 
		
		tablefn(self)[name] = new 
	end)
end)

 

This is definitely a case of overengineering.  While you can create some neat systems and get some performance out of it for your use cases, the major road blocks are going to be vastly larger in performance hits elsewhere than some additional functions residing in memory.

The work required to make function hooking moot would incur costs elsewhere, as ultimately the system must emplace a priority queue for when two mods interact with the same thing.  Whether it be a table that's iterated, or a series of nested function hooks down the line, the data will end up being roughly the same in the end.

  • Like 1
  • Sanity 1
  • Big Ups 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...