Using task.wait() inside parallel scripts reverts to serial execution

I have 2 actors, each with a copy of this script, one called “localscript” and the other “local2”

task.desynchronize()

local n = 10000
local iteration = 0
while true do
--fake work----------
	iteration += 1
	local tab = {}
	local t1 = os.clock()
	local ct = 0
	for i=1, n do
		table.insert(tab, math.random(1,10))
		ct = ct + tab[i]
	end
	local t2 = os.clock()
	if iteration % 60 == 0 then print(t2 - t1, "seconds to complete operations") end
----------------------------

	task.wait()
end

I’m using task.desynchronize at the top of the script which should run everything below in parallel, since the two scripts are parented to two different actors. In the release of parallel lua v2, the behavior for task.wait() was said to have been changed to:

Therefore, since the task.wait() is below the task.synchronize(), it should have been called in a parallel context, and resumed in the parallel context. However, if you take a look at the microprofiler, the two localscripts end up running on the same worker in serial.

I can’t seem to get the scripts to run on different workers, which defeats the whole point of parallel lua. After digging around I found this post with a similar problem, and this person had found the same serial execution with a particular script

Problems with Actors and Parallel Luau:

From their post and my post, it seems as if task.wait() does indeed resume in the same context that it is called in.
However, for whatever reason, that context reverts to serial, even if it is below a task.desynchronize or within a ConnectParallel callback, if the task.wait() is called in any scope other than the highest scope.

By the highest scope, I mean the scope of the callback, if connectparallel is used, or the scope of the script if desync is used. That means that if you place the task.wait() inside a for loop, while loop, if statement, or any other statement that creates its own scope, it no longer will run in parallel

event:ConnectParallel(function() --should run in parallel
  task.wait(1)
  print("hello")
end)
event:ConnectParallel(function() --goes back to serial for some reason
  if true then task.wait(1) end
  print("hello")
end)

All this is to say, I have no clue if this is an intended feature or a bug.

If this is a feature meant to discourage use of parallel cores for long computations, or discourage any work that can’t be divided easily into multiple cores and run in a single frame, (A star pathfinding for example), then it defeats one of the major use cases of parallel lua (having multiple pathfinding algorithms run simultaneously for example). If I want to run a massive pathfinding algorithm that takes multiple frames, I have to use task.wait() to spread that load and prevent framerate drops. Forcing serial execution means that I won’t be able to run multiple of such an algorithm.

1 Like

Howdy!

Based on what WheretIB said, the purpose of Parallel Luau is to logically split a task or a set of tasks in the course of one frame among different cores to offload the main worker.

Mini note: I can’t speak much of the implementation details, so what I say is only based on my experience and what I’ve read or discussed. And sorry for the lengthy reply.

So frames consist of a serial and a parallel phase, with multiple entries for switching the context. addTasksMulti finds available threads and delegates the tasks, with one of the actors usually running on the same main worker. waitUntilEmpty then makes sure that RunParallel lasts as long as the longest taking thread.


I’m a bit confused about task.wait() too, but everything indicates that the code after it still resumes serially. At this point I’m positive it was a miscommunication and something like this is maybe still being worked on. For now, the code is not meant to run in parallel with the rest of the engine.


What you said about the highest scope is pretty close to what I found by adding multiple ‘fake tasks’.

Different parts of the same script can be resumed at multiple points of the frame. It helps to think of the script itself as one big coroutine too.

script:GetActor():BindToMessageParallel("Message", function()
    -- parallel
    for i=1, 1e5 do
        local x = i^2
    end
    -- serial (next frame)
    task.wait()
    for i=1, 1e5 do
        local x = i^2
    end
    -- serial (deferred threads)
    task.defer(function()
        for i=1, 1e5 do
            local x = i^2
        end
    end)
    -- parallel again
    task.desynchronize()
    for i=1, 1e5 do
        local x = i^2
    end
end)

Result:

In case where task.wait() is in the scope of a block it seems the compiler can’t logically split the work so it’s just ran in serial.


Parallel Luau isn’t meant for completely simultaneous algorithms, but you can still get pretty powerful boosts from delegating a part of calulations. For an abstract example, let’s say 4 workers should do 200 calculations, 50 each, given by the main/messenger script. If that’s too much, the messenger could give 25 each and then 25 more in the next frame after task.wait().

There is some time loss while sharing the data between contexts, but it should be well outweighted by parallel work.

1 Like