Parallel Luau [Version 2 Release]

This is really awesome!

The addition of a messaging API will be super helpful for ergonomics, but I’d like to see a way to send messages out of actors as well. For example, an actor “emits” a message that any other actor or non-actor script could listen to.

I’d also love to see some kind of instance locking method, so an actor can “claim” instances and read/write any properties/methods in parallel on the condition that no other actors can do the same until the lock is released.

Super excited to see what comes next for parallel Luau.

4 Likes

@SuperDave6502 This is a big limitation because it prevents sharing many very common data structures. Consider a table mapping Player objects to how many points they have, or mapping a team (where teams are {leader: Player, members: {Player}, name: string} to what objectives they control.

The fundamental promise of Lua, and by extension Luau, is that tables map any value that isn’t nil or 'NaN` to any non-nil value.

6 Likes

This looks extremely promising! One question: how does a server decide how many cores are accessible to it? I’ve done some testing and found that my single player test world only had access to one core server side, which leads me to believe core count is scaled based on player count or historical load?

Any form of documentation on this would be massively appreciated, I’m guessing the algorithm for this relies on a lot of hidden data so even “server core counts are based on your game’s server demands” as an acknowledgement would be helpful.

6 Likes

While it may be inconvenient, I don’t think it’s actually that big of a restriction and is an understandable technical limitation. For your player mapping example, it is not terribly difficult and from what I’ve seen actually more common to use the player’s UserId in place of the Player instance.

7 Likes

So that’s the catch… I suspect the current iteration of SharedTable is more so targeted for convenience, not large scale performance. I have a large data structure with over 100k entries… I’m extremely curious to find out how this will fare for performance versus a regular non-actor table.

4 Likes

It is a big limitation. For example when using player identifier instead of Player, you have to consider that two different Player objects may share the same identifier if the same player rejoins the game. Also consider that some APIs take Player objects (such as firing to clients with a RemoteEvent), and is less typesafe (ignoring the lack of type safety with SharedTable), and that looking up player by user identifier may not return a Player, while a Player is guaranteed to have a user identifier.

Keying by user identifier is fundamentally less sound in many cases.

A Player key is only one common example. There are many more, such as the other one I just mentioned (yes for non-unique group names you’d need an identifier for networking anyway, but that doesn’t mean you want to key by that).

4 Likes

Nevermind, the whole AddToFilter issue was because of some oversights in my code. It works well to be honest. Tho any chance for perhaps a ClearFilter method? Could maybe save some time by just clearing a single one out every time rather than having to make new RaycastParams every time.

Also would it be possible to have GetSharedTable() return nil if the given string wasnt an entry created for it already? Would probably be more convenient than it returning an empty table.

3 Likes

Typically you should be deleting player data from tables when they leave the game in-order to clear up memory anyways, but if there are any cases where you do not, I cannot imagine why the same player rejoining and occupying the same key would be a bad thing.

Further, you can deserialize the UserId back into the Player instance for the API calls you mentioned quite easily with the Players:GetPlayerByUserId() method.

My point here is that while yes, it may be somewhat inconvenient at times, this limitation likely won’t actually prevent most use cases and is probably quite important from a performance standpoint.

My understanding is that parallel actors run in different Luau VMs, so enabling non-primative keys like tables and userdatas (Instances) would require synchronization of the internal identifiers for these datatypes which would create huge overhead if it is even feasible to begin with. Not worth it for convience in my eyes, but who knows, maybe it will be supported down the line.

If you have any further questions or want to continue discussing, feel free to send me a message. I don’t want to clog up this topic too much.

3 Likes

It is significantly slower than just accessing a normal table, but if you compare it to sending a table through a BindableEvent like you previously had to do, the results are a little better. My testing is showing iterating over an entire SharedTable takes approximately the same amount of time as sending it’s normal table equivalent through a BindableEvent.

cc @Fluffmiceter

edit: I should mention that this assumes you are just reading, writing by index, or writing with the SharedTable.increment() function. Using SharedTable.update() takes (understandably) significantly longer, about 10x the other methods.

2 Likes

Much anticipated and needed update: :+1:

I’ve updated my ActorPool module to support the new parallel luau 2 features.


Example

local ActorPool = require(game:GetService("ReplicatedStorage").ActorPool)

local myPool = ActorPool.new {
	baseModule = script.BaseModule,
	poolFolder = script.Actors,
	min = 1,
	max = 2,
	retries = 10,
	retriesInterval = 0
}

local myConnection = myPool:take()

local numsToAdd = SharedTable.new { 2, 4, 6, 8 }
myConnection:run(numsToAdd)
print(numsToAdd["total"]) -- PRINTS: 20

script.BaseModule

return function(numsToAdd)
	local total = 0
	for _,num in numsToAdd do total += num end
	numsToAdd["total"] = total
end
7 Likes

My use case involves only reads so hopefully performance in this case will be addressed with future updates. Maybe it would be best to copy over the data to each actor during loading so each VM can access the table at normal speeds? My concern is with the amount of memory that might consume, right now my level data takes about ~100MB in script memory.

2 Likes

I recall arseny talking about it a while back, it’s based on the average CPU usage of your game. Server resources are dynamically introduced to you the more you use them.

EDIT : Found it

5 Likes

Hello! I ran into an interesting issue, not sure if its a bug or I’m just not understanding something.
My setup: A single actor with the following script in the workspace:

task.desynchronize()
local r = 0

while(true) do
    print("busy work")
    for j=1,3000000 do --busy work, about 30ms
        r+=math.random()
    end
    task.wait()
end

What I expected: This loop would run completely parallel to the main game

What happens: It appears to block execution of the main thread, as though it is not parallel at all. eg: If this runs at 20fps, the whole experience in studio runs at 20fps.

Sorry if I’ve done something daft, but what is going on?

4 Likes

Tables are converted to a mutable SharedTable with limitations of a SharedTable.
Functions cannot be stored because of cross-VM limitation you mentioned.

2 Likes

I’ve responded to linked topic, to reiterate here, Terrain:ReadVoxels is callable in parallel and there are no mutex locks.
No plans for Terrain:WriteVoxels right now.

3 Likes

Parallel Luau is made to run many per-frame tasks in parallel with each other, not to run long-running tasks in parallel with the engine.

We are exploring some options for long-running tasks, but we haven’t decided anything concrete to make a promise.

2 Likes

Amazing update! I’ve recently started incorporating Parallel Luau into all of my new projects and found having to use BindableEvents to get data in and out incredibly annoying, so these new additions are greatly appreciated, but I find the inability to use BindToMessage outside of actor scripts a bit disappointing since I would really like to use it to also get output out of Actors and not just input, are there any plans to enable it for use outside of Actor scripts?

4 Likes

Okay thanks for the answer :slight_smile:

So now that actors get checked and resumed multiple times per frame, is there a way to execute code after all of the actors on a frame have run?

2 Likes

If you need to create one with each call then don’t bother. It’s a semi-solution to having to serialize large tables back and forth when communicating between actors. You’re meant to keep the same table semi-permanently and read/write to it as you need. You can create a very large SharedTable almost instantly by calling SharedTable.new(originaltable) though.

I’d really like to see them expand their API. I’d like to see a ToTable method, maybe also some level of access to mutex to speed up access. My guess is the overhead currently comes from it having to lock and unlock the shared table every time you edit a value, and there’s hardly any reason to do that 10 times in a row when you want to add 10 values. Maybe some kind of a bulk write method?

1 Like

So what I believe is the case is a SharedTable lives in a single area of memory that multiple Actors can access?