SimpleParallel - Conveniently speed up your scripts by utilizing Parallel Lua

This is my first open source module, I appreciate all feedback and constructive criticism!


Is your game suffering from low framerates? Is your microprofiler not looking so good? A potential solution would be to finally implement Parallel Luau.

In short, parallel lua speeds up your scripts by running things at the same time in multiple CPU cores, as opposed to just 1. (the default option).

However, the set-up process can be really tedious. I had to go through a lot of pain learning parallel lua too, which is why I made this module. It has very simple & comprehensible API, with almost unnoticable overhead, which makes it quite versatile.

Documentation & API
Here's a poorly-drawn schema explaining what the module does, and how it works:

1. Constructor

SimpleParallel.new(module: ModuleScript, workerCount: number, timeout: number?, name: string?) -> SimpleParallelInstance

Creates a new SimpleParallelInstance object.

  • Arguments:
    1. module: A ModuleScript instance which must return a function when required.

    2. workerCount: Amount of actors (CPU threads) to be allocated, of which sole purpose is to execute the module and return the results.

    3. timeout (optional) (defaults to 0, aka the next frame): If for whatever reason you want to yield your code, you may want to specify a timeout parameter. After ExecuteTasks is called and your code hasn’t finished before that, an error will be thrown.

    4. name (optional): The tag that will be shown on the microprofiler, so you can identify specific SimpleParallel instances.

2. Scheduling function calls (“Tasks”)

SimpleParallelInstance:ScheduleTask(...)

Pass any arguments that you want to pass to your ModuleScript’s returned function. The task will be scheduled in an internal table for future execution through the :ExecuteTasks function, listed below.

3. Executing scheduled tasks

SimpleParallelInstance:ExecuteTasks() -> {[number]: typeof(require(ModuleScript)(...))}

All tasks that were scheduled from :ScheduleTask() will be equally distributed across all Luau VMs. For the code to actually be ran in parallel, you need to use task.desynchronize() somewhere in your module.

This function yields until all tasks return a value. It returns an ordered table containing the returned value of each task.

4. Discarding ParallelInstances

SimpleParallelInstance:Destroy()

Destroys the actors and wipes off the table’s properties from memory.

Example Usage
-- ReplicatedStorage.FuncToExecute
local function work(count)
	local a, b = 1, 0
	
	for i = 1, count do
		a, b = b, a
	end
end

return function(count)
	local a, b = 0, 1
	
	task.desynchronize() -- Hop into parallel execution mode
	
	debug.profilebegin("Desync work")
	work(count)
	debug.profileend()
	
	task.synchronize() -- ... and back into single-threaded mode
	
	debug.profilebegin("Synced work")
	work(count)
	debug.profileend()
	
	return math.random()
end
-- StarterPlayer.StarterPlayerScripts.LocalScript

--!strict
local SimpleParallel = require(game.ReplicatedStorage.SimpleParallel)

local funcToUse = game.ReplicatedStorage.FuncToExecute -- (dont require it!)

local newParallelInstance = SimpleParallel.new(funcToUse, 4, 0, "HeavyWork")
task.wait() -- (only necessary in Deferred SignalBehavior. More details in the Precautions section in the devforum post)

while true do
	for i = 1, 4 do -- Best practice is to schedule as many tasks as there are actors, or less.
		newParallelInstance:ScheduleTask(75000)
	end
	local results  = newParallelInstance:ExecuteTasks() -- {number}
	
	task.wait()
end

Microprofiler:

Here’s the .rbxl file used for this

Precautions

1: Don’t call ExecuteTasks() at the same frame the ParallelInstance was created.

This is because of Roblox’s deferred signal behavior. When script.Enabled is set, the scripts aren’t immediately executed. Create your ParallelInstances on startup and use them later, or use task.wait() before using them.

Tips

1: Don’t overdo it

This module has (although relatively small) overhead due to bindable events. In cases where code is rather fast, it’s more beneficial to not use this module and just execute it in a singular thread.

2. Don’t spam task.(de)sync too much

Especially if you have more tasks than CPU cores (and actors), switching between parallel and serial mode may cause tasks to take more time to finish executing. Prefer switching from parallel ← → serial as little as possible in your function.

3: Limit the amount of tasks below or equal to the amount of actors assigned

Even though the tasks get evenly distributed among all actors, the overhead added by internal bindable events & task.desync() calls accumulates. For best performance and benefits, assign less tasks.


Get the module here! (.rbxm file)
Github Repository

12 Likes

Thank you boss

1 Like

This is good for your first module, it could have more parameters in order to be more of uh customizable, but covers the common use case of Parallel Luau

1 Like

thats crazy


UPDATE: Sorry for not doing this earlier, I replaced the link with a .rbxm file.

1 Like

Roblox moderation ahh moment, lol

1 Like

Update

  • Added github repository