If your question is ever “I want something changed, should I open a feature request?” the answer is probably yes.
Though I’d argue the common God Script layout for games is a bit lackluster anyway and it’s not taking advantage of Roblox’s environment properly to only have one script in your entire game.
I’m pretty new to this feature, and it seems to me like this video does a pretty good job of explaining the difference between regular code and parallel code.
I was excited to use it because I have three separate scripts which have their own RenderStepped connections, doing things like rotating coins and other map parts that are on the screen. I switched from messing with their CFrame properties to workspace::BulkMoveTo after I saw it cut down like 30% on the Script Usage percentage thing.
I don’t really know what this means. Why is it unsafe? What does unsafe even mean? Are there plans to change it? Is it better to stick to the old single threaded method with BulkMoveTo or switch back to regular CFrame changes but with this parallel scripting thing?
Apologies if this post sounds naïve about the whole concept behind this feature. I didn’t even know how to use the microprofiler before today, haha
There are some inherent limitations with multithreaded code, functions that modify the global environment (e.g. workspace) is one of them. Basically, when you have two independent threads trying to modify the same value, you can’t really be sure what will happen; who will run first, who will get overridden, what the value is actually going to be, etc. (this is called race condition). This inconsistency is what makes certain functions “unsafe” to be used in parallel code due to their unpredictable nature.
This is why there’s also the task.synchronize() function that will join back the parallelized threads. You’re essentially getting the threads to do a “handshake” and form an agreement on what is to be done by putting them back in chronological order. After parallelizing, you must re-synchronize the thread first before modifying the global environment again (albeit there are some exceptions)
I’m somewhat disappointed now that this is fully released. I know thread safety is important, but not being able to access common memory from different threads during sync operations makes this feature completely useless to me. Most use cases I have revolve around distributing large data operations over multiple threads, but this also means large outputs. So if someone knows a magical way to performantly send a 500x500 weight map through a BindableEvent (the total result usually contains 5-10 of them), please tell me as I’m starting to question whether I should keep using Roblox at all.
While this method of parallel execution is useful for some tasks, it’s useless for one of the biggest use cases of multi-threading, offloading singular heavy tasks to separate cores so they don’t bog down the main thread. Since the main thread always waits for all parallel threads to complete before continuing, offloading expensive tasks to another thread is useless. It’s also impossible to run tasks that take several frames to complete for the same reason.
I think a system that allows running a pure function on another thread and simply runs a callback when it completes would be fairly simple to add. I hope an API for this kind of use case is released in the future.
I also think a more efficient method of data replication between the threads is needed. Currently the only way to send pure data between threads/actors is to send it through a BindableEvent/BindableFunction, which has a high serialization cost. For my use case, the cost of sending the data was an order of magnitude higher than the cost of the operation itself.
I’m currently having an issue using the MicroProfiler with scripts utilizing parallel lua.
If you put a script inside an actor
And then you set up a simple script that runs in parallel
game:GetService("RunService").RenderStepped:ConnectParallel(function()
debug.profilebegin("Test Profile") -- setting up the microprofiler
task.synchronize() -- running code in serial
for i = 1, 5000000 do end -- lag machine for microprofiler
task.desynchronize() -- the issue runs with or without putting the script back in parallel
debug.profileend() -- warns in console
end)
As a result, you’ll see a warning in the output window that looks like this
This happens because when you run task.synchronize it changes the stack trace of the script since it’s moving it into the serial. As a fix to get around the flood of warnings in my output, I am forced to call debug.profileend anytime I want to switch between running in serial or parallel
The work around looks something like this:
game:GetService("RunService").RenderStepped:ConnectParallel(function()
debug.profilebegin("Test Profile")
debug.profileend()
task.synchronize()
debug.profilebegin("Test Profile")
for i = 1, 5000000 do end
debug.profileend()
task.desynchronize()
debug.profilebegin("Test Profile")
debug.profileend()
end)
Obviously it’s unrealistic how I’m handling the microprofiler labels in this sample code, but when you start having to work with something like a large renderstep update function that requires switching between running in serial and in parallel, and you need to create these microprofiler labels, the code starts getting messy real quick.
TL;DR
Will there be a simpler support for using the debug.profile functions when having to switch between running in serial or parallel?
Do roblox servers have Actors active? I have a script that works perfectly on the studio local server, but seems to run in Serial instead of Parallel on actual roblox servers.
After a bit of debugging, it seems like roblox server don’t have parallel lua active, only the client?
Is there any plan to allow us to access some sort of shared read-only data among actors? I have a custom raycast solution, and I would like to make parallel raycast calls, but there is no way that I’m aware of to read data from my BVH from separate actors without outright initializing said actors by sending massive tables over via bindableevents. I’m aware of the problems that come from having multiple threads reading and writing to the same memory, which is why I suggested the memory be read-only (can only be written to by one thread at a time, or maybe serial only?)
I have a bunch of questions regarding parallel Luau.
Do scripts have to be inside an actor to spawn multiple threads?
Could I for example… create 4 threads inside a single script? How?
And how does parallel Luau handle logic that makes use of module scripts and metatables?
In a module script (in ReplicatedStorage):
local class = {}
function class:new()
self.__index == self
local o = {}
setmetatable(o, self)
return o
end
function class:doParallelThing()
-- Parallel code
end
In a script that’s located inside an actor:
local object = require(PathToClass):new()
object:doParallelThing()
I use an object oriented design and I’m still figuring out how I can use parallel Luau with it.
Documentation and information on module scripts and how it all works is still limited and sometimes a bit vague.
I would greatly appreciate some clarification and explanation.
I noticed that if you create actors and destroy them after use, their memory stay and starts increasing if more are created and destroyed on the console, is this how it should actually work? shouldn’t the memory be cleaned if the actor was destroyed? or should we stick with a set number of actors instead of creating and destroying them when you use/finish using?
My current approach is to just have a folder with actors and parent scripts under it, if I no longer need them destroy the scripts instead of the actors.