Pathfinding Service Slows Down With Every Call

Reproduction Steps
I’ve found this issue in my own projects by calling ComputeAsync() using the Pathfinding Service and storing the results similarly to this:


local PFService = game:GetService('PathfindingService')

local paths = {}

local Path = PFService:CreatePath()

function MakePath(Start, End)
 local p = Path:ComputeAsync(Start,End)
 table.insert(paths,p:GetWaypoints())
 if #paths > 10 then table.remove(paths,1) end
end

Expected Behavior
I do expect some degree of slow-down after calling my MakePath() function quite a few times. I intentionally cull the paths table to only 10 entries to manage memory and it works. No other server stats or server scripts are affected by the script, ONLY the pathfinding service slows down.

Actual Behavior
Where it gets really strange is disabling the script calling the pathfinding service fixes the issue completely. I can even reenable it one step later and the pathfinding service still speeds up.

Even though I’m intentionally managing how much the script is keeping track of, there’s something happening internally that’s slowing down the pathfinding service, and it’s directly connected to that specific script.

Workaround
I’ve been calling the path finding service using delay and spawn so the slow-down doesn’t affect the script as a whole. But my path functions take a significant amount of time to finish after just a few minutes of being called regularly.

Issue Area: Engine
Issue Type: Performance
Impact: High
Frequency: Constantly
Date First Experienced: 2021-09-13 09:09:00 (-04:00)
Date Last Experienced: 2021-09-15 00:09:00 (-04:00)

1 Like

This is not the source of the slowdown. How often are you calling MakePath? Can you check how many waypoints are being created? Is that increasing?

I’ve narrowed it down to specifically ComputeAsync. That’s the only line in my code that takes longer and longer over time. I can’t check if the waypoints are increasing because I don’t store them over time. From a developer level, my script is basically letting go of the path information after a little bit of time. However it’s evident that it must not be letting go internally, because the pathfinding service takes longer and longer to run ComputeAsync until the script is disabled and re-enabled.

Also originally I didn’t have only one CreatePath call, I used to create a new path every time I wanted to call compute, it also slowed down then in the exact same way. So I don’t believe that’s the source of the issue.

Additionally the frequency of the calls doesn’t seem to matter as just the total amount. I want to say after about 50 total calls the slow-down becomes really noticeable.

1 Like

I just checked my pathfinding, and GetWaypoints returns a table you can just check the size of the path. Try this:

function MakePath(Start, End)
 local p = Path:ComputeAsync(Start,End)
--new code
 local test = p:GetWaypoints()
 print(#test)
--
 table.insert(paths,p:GetWaypoints())
 if #paths > 10 then table.remove(paths,1) end
end

For comparison, I have no performance issues constantly calling pathfinding services for up to 50 or so waypoints. If these tables are huge, then yes, you are slowing your program down by calling ComputeAsync repeatedly.

Is MakePath run in a coroutine? As if so, the slow down is expected.

I understand that, and yes I’m sure the tables are large and my environment is relatively complex. However if my script is removing the table after a time, and not referencing it anywhere, it shouldn’t continue to slow down.
Additionally, if the size of the tables were in fact that issue, why does it start off working pretty quick and get slower over time for tables that should be about the same size?

It’s not in a coroutine, no. I’ve wrapped it into a custom system that works similarly to coroutines to mitigate the slow-down, but my system is more efficient and doesn’t share any of the issues related to coroutines.

1 Like

Okay, it’s then likely the slow response times are because ComputeAsync is being called too much.

Ongoing ComputeAsync operations have to be stored in memory too!

But if that were the actual problem, why does disabling the script and immediately enabling it fix the issue completely? It’s not changing the frequency of the calls.

1 Like

Weird then, it could be that disabling the scripts cancells the pathfinding calls and free them from memory.

I think that’s most likely the issue, however if that’s the case it’s definitely something that can be fixed. Maybe if the script no longer references a specific path, it should be freed from memory automatically at that point.

1 Like

There might be some weird things going on when you overwrite Path. Do you need it once you store the waypoints in the table? If not, I would move the variable declaration inside the function and destroy it after use.

It was originally there actually, but the slow-down persisted. I moved it out to try and mitigate the slow-down.

Okay I read some other forum posts and it appears that a path creates a new table when it is called twice. That sounds like a potential memory leak to me. If you want to stop the bleeding, you need to destroy the path each time you are done with it. I can’t verify this, but it should be easy enough to test.

If this is the solution I will link the original post.

Well memory isn’t increasing over time, so I don’t think it’s a leak… but the path isn’t an object… it’s a dictionary. So you can’t destroy it, only set it to nil. Which I do.

Wait nevermind, it is an object… but this shouldn’t be an issue either since I use the same path object for all computes.

What they were saying is that every time you call ComputeAsync on the same path, the previous result is not disposed of from the path, you should use a different path every time.

I used to, and the problem was still there, so I don’t think that’s the source of the issue. Especially since 1 path with all of the waypoints is pretty close in size to just multiple separate paths with the same points.

Can you check the developer console to see if it’s a memory leak?