Please note: Modules were used in the example scripts for demonstrative purposes only, I’m well aware that modules are cached upon being required
As an example, let’s say I need to require some modules which are stored in a Folder named Modules, which is inside of ReplicatedStorage:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local modules = ReplicatedStorage.Modules
local module1 = require(modules.Module1)
local module2 = require(modules.Module2)
-- And then the rest of the code
Both the variables named ReplicatedStorage and modules are never used again within this script, so once they’ve served their purpose, they can be safely removed from memory
Now, I’m aware that local variables are automatically set to nil once the script exits the scope they were created in (unless a strong reference is held to the variable, like it being used within an active connection), but my question is: Since in this case the variables were declared within what essentially is the script’s global scope, is it still the case that they’re automatically set to nil if the conditions allow, or do we need to manually set them to nil as the example below demonstrates?
local module1, module2
do
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local modules = ReplicatedStorage.Modules
module1 = require(modules.Module1)
module2 = require(modules.Module2)
end
-- And then, once again, the rest of the code
Or a more simple alternative to the above:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local modules = ReplicatedStorage.Modules
local module1 = require(modules.Module1)
local module2 = require(modules.Module2)
ReplicatedStorage = nil
modules = nil
-- And then, for the final time, the rest of the code
You’re thinking about it wrong. The variables themselves aren’t set to nil, but the script is GC’d, which clears the memory used by the variables. You do not need to set variables to nil yourself.
Edit, further note: within a Lua VM (so an Actor), modules will stay around in memory. This is because when you require a module e.g. 3 times, it’s only evaulated once, and cached, and the following 2 requires just grab the cached value. So when you require a module, you can’t free that memory, unless you remove the actor that holds it (or shutdown the game for the global VM)
Wouldn’t that require the script to be destroyed first before the garbage collector can free it from memory? Instances, which scripts are, need to be destroyed and removed from any strong references first before the garbage collector can truly remove them from memory
Naturally, the usage is always lower after destroying. There is no difference between setting the variables to nil and not setting them (which supports my point, script is GC’d at its end even before it’s destroyed).
This makes sense: there isn’t much reason for a script to stay around in memory after it finishes. The GC picks up on this and poofs it out of existence.
Benchmark: a script starts another script. The latter script uses up some discernible amount of memory (I avoided using instances to ensure that we don’t get the engine secretly tracking stuff in the way). It measures the memory usage for scripts before starting the 2nd script, right before the data is created, and right after the second script finishes excution. For the “set vars to nil: true” run, 5 seconds are added to ensure that the GC could find time to run and clear up the variables.
Place with benchmark (use F8 to run without player), has an added delay of 5 sec before any benchmark starts: benchmark.rbxl (43,3 KB)
Created a new empty baseplate (I used the classic one since it doesn’t have the lighting Instances and spawn location) and deleted its baseplate part
I set Players service’s CharacterAutoLoads property to false
Created a server Script named Test inside of ServerScriptService, which has Enabled set to false
debug.setmemorycategory("TEST")
local a = table.create(999_999, 2^64-1)
Created a server Script (left the name default), which is also inside of ServerScriptService. This script was left enabled
debug.setmemorycategory("TEST")
local ServerScriptService = script.Parent
local test = ServerScriptService.Test
task.wait(30)
print("Start")
for _ = 1, 9999 do
local test = Instance.fromExisting(test)
test.Enabled = true
test.Parent = ServerScriptService
task.wait()
end
print("Stop")
I ran the benchmark using F5, not F8 (which is why I disabled CharacterAutoLoads). This was an intentional decision in-order to be able to view the dev console before the benchmark starts
I re-ran the benchmark, but this time I added script:Destroy() at the end of the script named Test
The memory category showed 0MB of memory at the start of both runs, and after the first benchmark concluded, 0.305MB was left in memory, while after the second benchmark concluded where Test was destroyed, memory returned to 0MB
The script is Garbage collected before you would even know it’s done.
The end of a script, if it has no loops or otherwise running code, will kill the current thread in the VM.
No, variables are not set to nil; they simply no longer exist, at all.
They’re gone until that script runs again in the next instance of the game (or when it is cloned again, an example of cloning being for A Chassis drive/GUI scripts being cloned into the Client when you sit in the DriverSeat object.)
Modules are indeed cached by the Luau VM as this saves memory since the Luau VM can just add an instance of that cached data to any script that next requires it to that script’s LuaState (LuauState?)
Furthermore, in the case of your benchmark, you should actually intentionally delay setting Enabled until after you have parented a Script, but only Scripts (both Server/Local) since otherwise your code may (although unlikely) have odd behaviors.
To clarify, by no loops I suspect you mean an infinite loop (while true do) that wasn’t created within a new thread
If a variable no longer exists, its memory address points to NULL, which is essentially nil in Luau
Not once did I doubt that modules are cached when required, I think the main post is being misinterpreted in this case. I only used requiring modules as a random example of a script, rather than to focus on modules specifically
Re-ran both benchmarks, this time enabling the Test clone after parenting it to ServerScriptService, as per your recommendation, and the results were the exact same as before
This isn’t correct from a CS theory perspective. For a variable to not exist, it must mean that:
nothing has access to it;
it doesn’t occupy any space in memory.
If a variable’s contents are 0x00 (NULL), that means the variable still exists in memory. If a variable is set to nil, it still exists. If a variable doesn’t exist, we cannot possibly be talking about NULLs, because it has no value. It’s like trying to imagine nothing - it’s weird, it’s difficult, but the variable simply doesn’t exist anymore.
NULL in C is directly equivalent to 0, which is technically a value even though it represents an empty quantity, so you’re correct in that sense
For a variable to truly cease to exist, it must be overwritten with the contents of a new variable, which can only safely happen once it has been freed, which happens either automatically once the script exits the scope the variable was declared in, or using the free function if the variable’s memory was allocated using the malloc function
To remove variable you need to remove or disable the script I guess. Anyway what is the point of this. You not increase/decrease any performance by this.
Just like it’s good practice to clean up after yourself in the real world, it’s also good practice to do so with scripts. A few megabytes there and there may seem insignificant, but when you think about it, freeing them from memory provides the same result as deleting textures or decals
Still arrays or variables takes nothing amount of space in memory. But even if u do there is no guarantee this memory will be freed because roblox do not even cleaning ram after game turn off. So u alawys need start client again. Especially it’s visible on high ram consuming games…
No. Even a RunService connection will still keep a LuaState alive, and prevent source Garbage Collection.
If there is still code that can be ran, or is running, that is reliant upon the script, then the script cannot be Garbage Collected by Lua’s GC.
This is general advice, the point is still mut and nil is an actual datatype that does take up memory within Lua and Luau.
The answer to this thread is: No.
When a script is garbage collected, there is nothing left of it. Variables no longer exist, as they have no reference nor value anymore, therefore there’s not even a chance for them to be “nil” as they cannot be accessed at all by any other code you may write. Even Roblox’s engine wouldn’t be able to recover the script’s memory sector unless it wasn’t freed, which it is.
Edit: And to cover your question:
No. This will actually end up taking up more memory than it will ever save you. You’re adding bloat to the bytecode after compiling, making this a pointless addition.
You’re actually not freeing anything. You have no control over memory management within Lua/Luau.
You can decrease the perceived overall memory by optimizing code and removing bloat from your codebase, but you cannot yourself do anything better than the Lua Garbage Collector does.
This is even further true with the version of Lua that Roblox created and uses, Luau, as they improved it to be more aggressive and thorough with its collection.
It helped me understand something important: So long as the script Instance exists, the script will always take up some memory, even if you’re careful about setting each variable to nil. This is because for a script to truly no longer exist, it must be destroyed, just like any other Instance. Essentially if you monitor the script, and confirm that no memory leaks are occurring, and keep its code clean and organized by creating only variables that are necessary and removing values from tables when they’re no longer needed, you’re good to go
As @Pixeluted provided, the Lua 5.1 Manual goes over a brief outline of the Garbage collector.
Further info is found for Luau here: Performance - Luau
It documents every single improvement the current version has in terms of performance (obviously not all or it gets pedantic with micro-optimizations and some macro-optimizations, but you get the idea.)