Memory leaks should not really slow down your game afaik, at least, not much, memory does not have a CPU time cost unless there is code accessing or modifying that memory. A memory leak is just memory that isnât being collected by the GC for some reason, so, thereâs not really much CPU cost, at least, not anything that should be noticeable or consistent.
Most memory leaks will introduce some small cost, since the GC is checking if it can collect that stuff, and, most memory leaks are caused by it being unsafe to delete data because its considered in use. The garbage collector only periodically checks values, I believe about once every 5-10 seconds last time I checked, so, youâd really only see a small increase in frame time every 5-10 seconds and probably spread out over a second or so.
Several GBs of untracked memory is not normal afaik, a typical amount of untracked memory should be under a few hundred MBs at most, and, thatâs consistent across a few large games I have worked on.
Error code 277 refers to DisconnectConnectionLost
on the ConnectionError Enum page, in otherwords, this would imply a hard crash on the server side. (P.s. this Enum page is surprisingly not well known about, it actually gives some really good insight into different error codes and gives you a lot of useful context)
If I had to guess, your problems are likely due to a memory related crash. I know on OSes like Windows when the system runs out of memory programs will be silently and instantly terminated. (If youâve ever seen an out of memory error, iirc this is actually done by the program, and, is different than the system running out of memory. Java will allocate a set amount of memory, and, once that set amount of memory runs out it will give you an out of memory error. I believe it also refuses to allocate too much memory once the system gets to a certain point)
@MetatableIndex
This isnât actually true, I believe youâre misunderstanding the bug report you linked (or maybe you just worded it wrong)
:Destroy()
does disconnect all connections, you can test this with a
BindableEvent
easily:
local bindable = Instance.new("BindableEvent")
bindable.Event:Connect(print)
bindable:Fire("Before destroy")
bindable:Destroy()
bindable:Fire("After destroy")
The problem discussed in the linked post is that when :Destroy()ing an object from the server it only replicates the ancestry change (so its like setting Parent = nil
), meaning on the client the object isnât cleaned up. This is actually a super interesting issue that I didnât think about.
Additionally, after thoroughly investigating @l_11Iâs code I determined its probably not actually a memory leak related to the touched connection not being disconnected, rather its actually a memory leak due to the use of Debris (its a direct consequence of the thread scheduler being completely overloaded). If you remove the Touched
connection the memory leak definitely still happens its just about 60-70x slower because when using it an extra instance (TouchTransmitter) is created and the connection itself, the physics data, and the data extra data the TouchTransmitter is adding for replication stuff is a lot relatively speaking. You are creating about 450 parts, and, doing 450 schedules per second. That is a lot of schedules per second.
The following code still produces the memory leak:
local Debris = game:GetService("Debris")
local bullet = Instance.new("Part")
for i = 1,30 do
spawn(function()
while wait(0) do
local partCopy = bullet:Clone()
Debris:AddItem(partCopy,0)
--partCopy.Touched:Connect(function()end) -- This line causes memory leak
partCopy.Parent = workspace
--partCopy:Destroy()
--print(#workspace:GetChildren())
end
end)
end
The following code does not:
local Debris = game:GetService("Debris")
local bullet = Instance.new("Part")
for i = 1,30 do
spawn(function()
while wait(0) do
local partCopy = bullet:Clone()
--Debris:AddItem(partCopy,0)
partCopy.Touched:Connect(function()end) -- This line causes memory leak
partCopy.Parent = workspace
partCopy:Destroy()
--print(#workspace:GetChildren())
end
end)
end
And finally, this code does not either (I use coroutines because they do not rely on the thread scheduler like spawn which would make it extremely slow but also the thread scheduler appears to be the underlying issue):
local Debris = game:GetService("Debris")
local bullet = Instance.new("Part")
for i = 1,30 do
spawn(function()
while wait(0) do
local partCopy = bullet:Clone()
--Debris:AddItem(partCopy,0)
coroutine.resume(coroutine.create(function()
wait()
partCopy:Destroy()
end))
partCopy.Touched:Connect(function()end) -- This line causes memory leak
partCopy.Parent = workspace
--partCopy:Destroy()
--print(#workspace:GetChildren())
end
end)
end
If you try the prior snippet with spawn you will notice the memory leak reappear. (You also have to remove the wait()
call for consistent speed to the Debris example because spawn
has a minimum delay, another reason to not use it or other thread scheduler based things).
Additionally, wait
relies on the thread scheduler. I went into detail on why using wait
for non-delay related things is bad in another post somewhere but Iâll summarize it here. Basically, wait
is a lot more lightweight than spawn
or in this case Debris
and its synchronous with your script so its not as bad, but, wait
can still throttle and utilizing it for non delay related code (e.g. an infinite loop) can actually cause gradually worsening lag and memory problems as it slowly throttles more and more.
You should only use wait (and thread scheduler things) for what theyâre meant for: delays. You can use wait for quite a few loops before you will start seeing any problems, and, its about 30x less impactful per second youâre waiting for when used in a loop, but, its not great to do that, and, too many loops means youâll hit a point where your game starts getting inexplicably laggy and inexplicably memory intensive on the server (or client).
Instead of while wait() do
use one of the RunService events instead. For example, RunService.Heartbeat:Wait()
. Despite being a loop thatâs twice as fast, the impact it has is significantly less than that of wait
when used in bulk.
That goes for spawn
too.
The complexity of thread scheduler use scales up very quickly, unlike using Heartbeat
. Additionally, youâll even see that your game is smoother when not using wait()
because Heartbeat
happens before data is sent to the client from the server, its kind of like RenderStepped
, except for data going to the client.
Lastly, hereâs a list of things that use the thread scheduler and should be avoided in bulk (Using these is not a problem, using them a lot is):
wait
- Not too impactful since its fairly lightweight, its just pertaining to the thread it was called from. Can cause really bad throttling issues in bulk (hundreds of wait
calls happening at once per second).
spawn
& delay
- Very impactful. Both are basically equivalent. This creates an extra thread, and for some reason is just ridiculously slow. It has a huge impact on throttling, probably the worst.
Debris
- Iâm not really sure what the impact of this is but based on my testing it seems to be roughly 0.7x as impactful as spawn
and delay
(Based on the memory usage). It likely just doesnât create threads internally, and, if it doesnât, probably skips some unnecessary stuff related ot lua contexts or something.
Certain things like wait
, spawn
, and delay
(and probably Debris
since it appears to behave very close to delay
) will throttle, meaning usage of them creates âfakeâ lag.
Additional information:
Task Scheduler (roblox.com)