Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Multithreading or similar in Unity?

Discussion in 'Scripting' started by LaphicetCrowe, Mar 21, 2021.

  1. LaphicetCrowe

    LaphicetCrowe

    Joined:
    Sep 30, 2020
    Posts:
    17
    Everywhere I check, I'm reading about how non-job system Unity isn't threadsafe and how the entire game runs on one main thread. I'm utterly baffled by how this works. There are a ton of big games ('big' as in big scenes with lots going on) that predate the C# job system and ECS such as Subnautica or 7 Days to Die. I'd figure if these games ran off only one thread, the game play would be cripplingly slow or the player's CPU would be in full swing.

    I'm not saying they do or don't. Maybe I'm underestimating the power of a single-threaded game. I used ILSpy to take a peek in these games to get an idea how they accomplished this, but nowhere do I see anything about threading. I mean, sure, it's likely they're practicing good performance techniques, but for games of those sizes, I have my doubts that that alone is allowing good performance on one thread.

    However, I don't know anything on how I'd squeeze out performance like that in my own games. I assume even as of Unity 2020.3, async-await is still a no-no?
     
    marcospgp, synthc and Haxel0rd like this.
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,935
    It's entirely possible they are completely single threaded.

    It's also entirely possible they do some processing on other threads. There are serious limitations to how you can directly access Unity objects from other threads, but the entire System.Threading and System.Concurrent namespaces are available for use.

    The other thing to consider is that besides CPU multithreading, there are other ways to squeeze extra performance out of your hardware in Unity. Just a couple examples:

    https://docs.unity3d.com/Manual/class-ComputeShader.html
    https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html
     
    Haxel0rd likes this.
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,816
    Because some game are open world, doesn't mean it requires multi threading.
    There is indeed tons of way to optimize things, before threading is any sensible use for.

    Yet even many large (1k + units) scale RTS games, are just single threaded. And that mostly because of determinism challanges.
    WoW is also single threaded, while world is massive. Maybe they ofloaded some part of code by now. But there isn't that much happening. Unless having many characters and NPC in big cities, in small area. But usually that GPU bound.

    While most of games run single threaded, ECS is nothing new, neither threading is.
    CitySkyline I think uses Entitias, which was made and sold on AssetStore long before Unity ECS, was even available.
    Subnautica possibly also using Entitias.
     
    Haxel0rd, exiguous and PraetorBlue like this.
  4. LaphicetCrowe

    LaphicetCrowe

    Joined:
    Sep 30, 2020
    Posts:
    17
    I see. Thanks. Gives me a bit to think about.
     
  5. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    There are lots of people complaining about 7 Days to Die's performance ;).
    And the action is only centered around the player. Often you can see zombies/animals/buildings beeing spawned. So not the whole world is simulated but only the small part the player can see. The terrain meshes are generated procedurally on demand. So when you travel through the world it is large, but only a small part of it is "active" and simulated at any given time. Even in the ECS Megacitiy demo only a part of the buildings is active/loaded. So don't let them "fool" you about the processing power such games need. Those games are just good at omitting "unecessary" work.
     
    Antypodish likes this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,125
    And almost everything about computer graphics in general, and ESPECIALLY realtime interactive graphics is:

    - how to cleverly not do the work

    That's literally the secret. It is closely related to the concept of a Potemkin Village, a thin veneer or facade designed to give the impression of a robust and functional larger world:

    https://en.wikipedia.org/wiki/Potemkin_village
     
  7. Wulirocks

    Wulirocks

    Joined:
    Mar 18, 2013
    Posts:
    63
    Hey, Learning lot from this thread, but I want to thank you for this reply. Particularly "how to cleverly not do the work" is really the thing to remember when entangled with complex code. Potemkin village is really the perfect analogy.
     
    Kurt-Dekker likes this.
  8. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    For those coming from Google (this is currently the #2 result for "unity multithreading")

    Unity introduced async/await support in version 2023.1 with the Awaitable class that is meant to stand in for Task.

    Previously, Tasks were already usable but one had to pay attention to some details such as pending tasks not being stopped automatically when exiting play mode in the editor.

    It's unclear to me how necessary it is to use Awaitable over Task and whether the previous issues with Task were fixed.

    The main catch in multithreading with Unity is that a lot (if not most) of the engine's classes can only be accessed from the main thread.

    From what I can tell, the main benefit from the introduction of Awaitable is that coroutine syntax is no longer needed for things such as waiting for the next frame. Previously, one would have to implement a sort of shim with async code interfaced with coroutines.

    This example in the documentation does lead me to believe that .NET Tasks are supported:

    upload_2024-1-5_15-43-4.png
     
    Last edited: Jan 5, 2024
    SisusCo likes this.
  9. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,935
    Unity async/await support is still mostly single-threaded though. It's more or less a flexible alternative to coroutines.
     
    bugfinders likes this.
  10. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    This is not the case - Unity supports multithreading and has for a long time, even before the introduction of Awaitable.

    But I see how one can think otherwise - and I missed this in my previous comment. Awaitable has a somewhat clunky way of running code outside the main thread.

    One has to do
    await Awaitable.BackgroundThreadAsync();
    to resume execution outside the main thread:

    upload_2024-1-5_15-51-27.png

    With the .NET Task class, Task.Run() will do the same in a more semantic way, so that's what I'll continue to use.

    There is also an "Async, tasks and Awaitable" section in the "Overview of .NET in Unity" documentation page, which previously was "Limitations of async and await tasks" and before that was "Avoid using async and await".

    This page says:

    > Unity doesn’t automatically stop code runnining in the background when you exit Play mode. To cancel a background operation on exiting playmode, use Application.exitCancellationToken.

    So I'll continue having to use my SafeTask implementation.
     
    Last edited: Jan 5, 2024
    LauraAmaro, trombonaut and SisusCo like this.
  11. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,935
    Right - in other words you have to go out of your way to get things to run on a different thread, and this still leaves you with all the normal limitations of multithreading. It's basically just a quality of life improvement vs using System.Threading.
     
    Bunny83 likes this.
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,125
    Almost universally when I see people (EDIT: people in this forum using Unity3D) reaching for multithreading or multitasking, this is what happened:

    - I have a game idea
    - I tried a simple brute force implementation
    - oh my gosh it is unbelievably slow on mobile
    - go to the internet and google
    - read "multithreading can make programs faster!"

    Now people go to their Unity project with this one giant multitasking hammer they found and start smashing everything flat with it, making everything multithreaded and multitasked and doing all this crazy complicated work, obfuscating the original flow of their program, almost always introducing new bugs, new edge cases and complicated use requirements.

    Finally they test it on the actual hardware. It didn't get any faster.

    But their tech debt sure went up! What used to be simple in their project ("spawn a creature!") has become a multithreaded process of firing up a task, creating, configuring and injecting a creature specification object, sending it off to run on the creature-making thread, then hooking it back in using some kind of delegate marshaling scheme... and...

    The game is still slow.

    Well, turns out that the game was originally slow because they were pushing too many pixels, not because they were running everything on the main thread.

    Again, I stand by what I said above:


    ALSO:

    For all performance and optimization issues, ALWAYS start by using the Profiler window:

    Window -> Analysis -> Profiler

    DO NOT OPTIMIZE "JUST BECAUSE..." If you don't have a problem, DO NOT OPTIMIZE!

    If you DO have a problem, there is only ONE way to find out: measuring with the profiler.

    Failure to use the profiler first means you're just guessing, making a mess of your code for no good reason.

    Not only that but performance on platform A will likely be completely different than platform B. Test on the platform(s) that you care about, and test to the extent that it is worth your effort, and no more.

    https://forum.unity.com/threads/is-...ng-square-roots-in-2021.1111063/#post-7148770

    Remember that you are gathering information at this stage. You cannot FIX until you FIND.

    Remember that optimized code is ALWAYS harder to work with and more brittle, making subsequent feature development difficult or impossible, or incurring massive technical debt on future development.

    Don't forget about the Frame Debugger window either, available right near the Profiler in the menu system.

    Notes on optimizing UnityEngine.UI setups:

    https://forum.unity.com/threads/how...form-data-into-an-array.1134520/#post-7289413

    At a minimum you want to clearly understand what performance issues you are having:

    - running too slowly?
    - loading too slowly?
    - using too much runtime memory?
    - final bundle too large?
    - too much network traffic?
    - something else?

    If you are unable to engage the profiler, then your next solution is gross guessing changes, such as "reimport all textures as 32x32 tiny textures" or "replace some complex 3D objects with cubes/capsules" to try and figure out what is bogging you down.

    Each experiment you do may give you intel about what is causing the performance issue that you identified. More importantly let you eliminate candidates for optimization. For instance if you swap out your biggest textures with 32x32 stamps and you STILL have a problem, you may be able to eliminate textures as an issue and move onto something else.

    This sort of speculative optimization assumes you're properly using source control so it takes one click to revert to the way your project was before if there is no improvement, while carefully making notes about what you have tried and more importantly what results it has had.

    "Software does not run in a magic fairy aether powered by the fevered dreams of CS PhDs." - Mike Acton
     
    Last edited: Jan 5, 2024
  13. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    Well, calling Task.Run() is a similar effort - you'll always have to explicitly specify you want to run something outside the main thread, no?

    I do agree Awaitable is mostly a modernization of the whole Coroutine API, and doesn't seem to fundamentally change the way Unity handles multithreading (as evidenced by the fact that pending tasks still continue running after exiting play mode).

    Unity's SynchronizationContext (linked to from one of the doc pages I linked to previously) seems to have received only a small update upon the introduction of Awaitable.

    Edit: I noticed that
    Awaitable.BackgroundThreadAsync()
    is essentially a wrapper around
    Task.Run();
    , so the two shouldn't be very different in behavior.
     
    Last edited: Jan 5, 2024
  14. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    Not sure what prompted this reaction, multithreading is a valid topic and a core aspect of software engineering.

    Your advice is valid however, and may be helpful for people landing on this thread that could benefit from the knowledge.
     
  15. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,125
    It's not a reaction. It's an observation from reading a great deal of posts here asking the same questions over and over again about multithreading and improving the performance of a naive implementation.

    Of course it is! I never said otherwise. Go read my post again.

    My post is narrowly framed to all the Unity3D users posting here having some flavor of multi-whatever problem because they were trying to do it to address a performance issue in Unity.

    In fact, in over a decade here I have not seen a single attempt at multi-threading succeed in improving performance.

    If you are in this forum asking questions about using multithreading to make your game run faster (the original poster three years ago used the words:

    So clearly, they're already in the cohort of ALL the other reaching-for-multi-threading-for-performance posters on this forum that I've seen.

    Thus I am surfacing my basic observation again about this problem space.
     
    alexandr13568, Anthiese and Bunny83 like this.
  16. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,253
    Probably because you don't look at the Jobs sub-forum ;u
     
  17. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 5, 2024
    Posts:
    536
    The most notable thing about Unity's Awaitable is that it is pooled, so it shouldn't allocate on every single await. It's a theory, I didn't get around to actually test them against each other with Task. (In exchange you shouldn't await on it more than once.)
     
    SisusCo likes this.
  18. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,553
    Are we counting running the code in a separate thread to process in a single-threaded manner? One of my past projects had a rope simulation handled in that fashion. :p
     
  19. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,125
    That sounds neat, a little custom physics engine running in another thread. That's a PERFECT use of multi-threading.

    From one engineer to another I look at that, nod knowingly at you and give you this big "Hey cool setup bro!" look and you and I know we both appreciated your work in that.

    But also... from one engineer to another, we know the vast majority of posters in here asking why they're having multi-tasking woes are not people in any danger of implementing their own physics system. :)
     
    Ryiah likes this.
  20. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 5, 2024
    Posts:
    536
    But also... just because not everyone is implementing physics on separate threads, that doesn't mean we should run around and tell everyone how multithreading is bad, IMHO. I think the best approach if we tell them that they need adequate workload to worth something putting on a separate thread and those things should not rely on extensive logic interfacing Unity-data extensively.

    But maybe it's just me.
     
    marcospgp, Ryiah and Kurt-Dekker like this.
  21. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    1,008
    I think Kurt's words are a bit miscommunicated here. He's not saying multithreading is bad. He's saying most people who come to the forums with questions of why it isn't helping their performance are not knowledgeable enough to understand what the fundamental problem is. And the people that *do* understand the problem generally know how to solve it - with or without multithreading as they see fit. Which implies they won't be the ones showing up on the forums with such issues. That was my take anyway. Sometimes I do read between the lines a bit though.
     
    Nad_B and Kurt-Dekker like this.
  22. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    Well a Task is just another class, and unless one is allocating a few thousand per frame I don't think the overhead should have a noticeable effect.

    It's likely the engineers that thought of that may have also worked on Unity's job system and had the same paradigm in mind of setting up async jobs as tiny tasks that contribute to a larger algorithm, but in my mind Task.Run() is more about launching longer lived jobs... I'm not sure though maybe it does make sense to pool Tasks.
     
  23. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 5, 2024
    Posts:
    536
    I know there is this notion among some Unity developers that GC is "not that bad" and "people won't notice", but the harsh reality is that it is bad and people do notice. (Hence the mild rumor on the internet that it is impossible to make Unity games which do not stutter...)

    GC run is something hard to avoid, but often worth the effort because maybe your tests don't show hiccups in your game, but people with devices running other things will or it limits your design by artificially limit how much CPU you can use because you have to accommodate this spike... sometimes. It is somewhat better with the "new", incremental GC, yes, but it is still a problem, valuable CPU is used on doing work not related to the game in the sense it doesn't contribute to the player's enjoyment and engagement. From game design standpoint it's valuable but wasted milliseconds. You can try to do it manually and run GC when your game is on somewhat downtime, but then you almost do as much work and compromises on your architecture as if you were aiming for the allocation free run. Or at least near-allocation free.

    Not to mention that the amount of stuff people is saying that "it is fine", add up. Saying that useless waste of CPU power to one thing is fine in isolation is asking for trouble when you realize you said this to many things already and they are biting into your AI ms-budget or into some fancy effect's ms-budget.

    Rant over. :)
     
    Bunny83, Sluggy, Nad_B and 2 others like this.
  24. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    I couldn't agree more. Zero GC all the way!
     
    Bunny83 and Nad_B like this.
  25. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,584
    I'd argue the concern of stutter and GC really has more to do with the kind of game you're playing/making.

    If you're trying to make a game that is so fast paced and twitch accurate... yeah... stutter from GC could be a huge concern! Making the new Counter-Strike in Unity, a game played for years after release for its high frame rate and smooth action... probably not so fun with some stutter.

    But some more regular paced game about wandering the country side picking mushrooms that has a frame drop every once in a while... so what.

    I mean yeah... we don't want GC running once per second in ANY game. But... GC running infrequently causing a frame drop from 60 to 30 in a game that would feel fine at a constant 30 regardless... so what?

    I mean sure... some hardcore "games should NEVER stutter" person could come out of the woodwork with some idealistic expectation of all games. But... I don't cater to that person. If you don't like my game cause it's not perfect... welp... don't play my game. I don't care.

    I haven't made a game yet that needs that twitch perfect gameplay. I keep awareness of my GC to a reasonable extent... I use UniTask instead of tasks, I have some general object pooling, I don't run 1000 linq statements per frame. But hey... say there's some interaction event in my game that happens in the minutes rather than the milliseconds. Yeah... I'll use me some linq to save the annoyance of writing a garbage free version of it. I'll drop some class instances if needed that get thrown in the garbage heap. I don't care enough...

    Cause as @Kurt-Dekker loves to mention a lot on these forums... I'm here to make games.

    The next time I'm trying to make the next Call Of Duty killer... well... the first thing I'm going to have to do before figuring out how to write it garbage free is look at my bank account to see if I have the budget to even take that on.

    Case in point... my fave game that I would play daily if I had the time to is "Risk of Rain 2". It has stutter. You loop enough times that the screen fills with baddies the game strait comes to a < 1 frame per second rate. And honestly... IT'S AWESOME watching the computer try to keep up with the million baddies blowing up on screen that it screeches to a hault... looks like it might actually freeze... then slams back up to 100fps as a bajillion monstertooth health drops and potted health planters spawn into existence for a short 1 second respite before another swarm of enemies comes your way.
     
    Nad_B, Ryiah and CodeRonnie like this.
  26. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    I hope nobody takes me the wrong way. One thing I've learned from focusing a lot on reducing GC is how to be more comfortable with allocating garbage at appropriate times. If a thing happens once, at the start of your game, putting in extra effort not to allocate is a total waste. Just let the good 'ol garbage collector do what it does best. If the Profiler doesn't show an issue, then it doesn't really need optimization.
     
    Last edited: Jan 6, 2024
  27. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    I found some interesting information in Awaitable.cs:

    Code (CSharp):
    1.  
    2.     /* Awaitable Coroutines are async/await compatible types specially tailored for running in Unity. (they can be awaited, and they can be used as return type
    3.      * of an async method)
    4.      * Compared to System.Threading.Tasks it is much less smart and takes shortcuts to improve performance based on
    5.      * Unity specific assumption.
    6.      * Main differences compared to .net tasks:
    7.     - Can be awaited only once: a given Awaitable object can't be awaited by multiple async functions. This is considered undefined behaviour
    8.     (considering it undefined behaviour allows for optimizations non-achievable otherwise)
    9.     - Never capture an ExecutionContext: for security purposes, .net Tasks capture execution contexts on awaiting to be able to handle things like
    10.     propagating impersonnation contexts accross async calls. We don't want to support that and can elliminate associated costs
    11.     - Never capture Synchronization context:
    12.     Coroutines continuations are run synchronously from the code raising the completion. In most cases, it will be from the Unity main frame (eg. for Delayed Call Coroutines and existing AsyncOperations)
    13.     Methods returning awaitable coroutines with completion being raised in a background thread will need to mention it explicitly in their documentation
    14.     - Awaiter.GetResults won't block until completion. Calling GetResults() before the operation is completed is an undefined behaviour
    15.     - Are pooled object:
    16.     Those are reference types, so that they can be referenced accross different stacks, efficiently copied etc, but to avoid too many allocations,
    17.     they are pooled. ObjectPool has been enhanced a bit to account to avoid Stack<T> bounds checks with common get/release patterns produced by async state machines
    18.     - Can be implemented in either managed or native code:
    19.     There is Scripting::Awaitables::Awaitable base class providing interop with this managed coroutine type, that can be extended so that
    20.     Native asynchronous code can be exposed as an await-compatible type in user code.
    21.     See AsyncOperationCoroutine implementation as an example
    22.      * */
    23.  
    But as I mentioned before there is still a call to Task.Run() in the background thread code, so won't an Awaitable allocate a Task just the same?

    So far the only reason not to use Task.Run() directly was if I could avoid allocating Task objects by instead using Unity's Awaitable with
    Awaitable.BackgroundThreadAsync
    , but apparently it's just a wrapper for Task.Run().

    And if we only avoid allocations in synchronous code, there already is a ValueTask type for that purpose. So Awaitable seems like an over engineered wheel reinvention?

    They could have just introduced ValueTask-returning methods that replaced the legacy Coroutine API.

    I wrote some notes on this here as a way of organizing my thoughts - any feedback or corrections are appreciated.

    On that note, what do you all think about how the Unity job system compares to Task API multithreading? It seems to me that Unity tried to reinvent the wheel there, with a similar setup to .NET's thread pool and work allocation. The docs have this to say about it:

    Maybe this has to do with the overhead of C#'s Task API, but it seems to me one could get around this with some lower level code and avoid getting into a very proprietary and seemingly convoluted job system.
     
    Last edited: Jan 8, 2024
    SisusCo likes this.
  28. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,584
    Who said the only reason to avoid Task.Run directly was if to avoid allocating a Task object?

    Awaitable's design appears to be mostly about bringing Unity specific coroutine behaviour to async/await. Where flow control is controlled by the 'await' command similar to 'yield' in a coroutine. To use a Task.Run to get on another thread requires a callback function and results in code dis-similar to how Coroutines are generally written. Where as Awaitable.BackgroundThreadAsync and Awaitable.MainThreadAsync allows you to hop between threads inline in your code.

    I feel like this train of thought is spurring from a response PraetorBlue had about async/await being mostly single-threaded. Which was specifically a response to you necroing this thread to talk about async/await in a thread about multithreading. They were pointing out that async/await isn't specifically about multi-threading... it's about async code.

    The quick take on that by PraetorBlue is probably related to the fact that a lot of people on the forums assume async/await is about multi-threading because they conflate "asynchronous" with "threaded"... which the 2 are not the same thing.

    So when PraetorBlue said:
    What they were saying is that you have to take an extra step to bring multi-threading into async/await. May that be by calling Task.Run, Awaitable.BackgroundThreadAsync, or even just creating a thread (which isn't easily intertwined with async/await without creating various signals between the 2... which is the entire point of Task.Run, it sets up those signals).

    The job system is completely different from async/await and tasks and the thing you quoted even says that:
    As the quote says async/await is good for asynchronous operations and long running tasks. It's for asynchronicity! We have a thing that may take multiple frames and we don't want to block the main thread while that happens. Like waiting for a web request to come back. Effectively the same thing coroutines do which also aren't threading. They're asynchronous.

    The Job System is specifically part of Unity's DOTS. DOTS stands for "data-oriented technology stack" and as the name implies its design is all about data oriented design!

    This is why the above comment says:
    A really rough simple explanation of data-oriented design is... we orient our engineering approach about how the data is stored in the system. We can optimize our workflow by pumping data quickly through the CPU in a long sequence of data and performing quick repetitive operations on said data. The threading comes in by allowing every CPU to work on that set of data in stream-lined chunks simultaneously.

    As you say:
    Yeah... that's what the Job System sort of is. Unity has done the heavy lifting of setting up the lower-level demands of organizing the data for a data-oriented approach. This way all you have to do is write the small quick repetitive operation that will be performed on the operation. This is why jobs use a lot of things like the 'NativeArray' type which is just a wrapper struct pointing directly into the unmanaged memory on the engine side of things where they've written the low-level C/C++.

    For example... lets say you wanted to animate every vertex of a mesh that had thousands if not millions of verts. You just wanted to apply some like sinusoidal undulation to the mesh in the y direction. This is a fairly simple operation... the problem is that you need to perform it a million times and you need to do that before the frame is over.

    see the whole "short lived" aspect there?

    So what DOTS and the Job System does is prepares all those verts in a data-oriented manner so that we can stream across the chunk of memory efficiently and then use every core of the CPU in tandem to operate on that data at the fastest possible rate.

    (you may wanna reach for something like a compute shader here which depending on the data you're wanting to operate on could work... for another example see: https://catlikecoding.com/unity/tutorials/basics/jobs/ where they're animating transforms around in a fractal, since its animating transforms using compute shaders would be more convoluted)

    ...

    How would write that in async/await? And I guarantee whatever method you're thinking right now... is NOT as performant. That is unless you wrote a lot of boilerplate to effectively have your own data-oriented approach that well... is more boilerplate than the jobs system.

    ...

    Effectively they serve different purposes:

    async/await - to perform asynchronous operations over time without blocking the main thread. The time is predominantly taken up by a lot of "waiting for some operation to complete". May that operation that is completing be on another thread, or if that operation is just asynchronous in nature. Waiting for a webrequest to return, waiting for the player to press a button, waiting for some time to pass... a lot of "waiting"... hence the term "await".

    jobs - to perform short bursts of repetitive operations distributed across every CPU core on large chunks of data. It's not to dissimilar to compute shaders in that you're leveraging the multiple CPUs like how a GPU leverages multiple compute units.

    Give you an idea...

    async/await replaces coroutine

    jobs optimizes something where having the same Update script on a million objects brings your computer to a screaming halt
     
    Last edited: Jan 8, 2024
  29. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
    So the reason to avoid
    Task.Run()
    is to avoid C# style threading in favor of a Unity-specific style? I tend to prefer sticking to higher levels of abstraction so my knowledge translates better to other areas, and C# is definitely at a higher level than Unity.

    I explicitly mentioned this thread is a top search engine result, and the activity that followed showed that the discussion is relevant and worth having.

    I don't see how the distinction between async and threading is as relevant as you make it out to be. I was specifically comparing the threading approach of C# Tasks vs Unity's new Awaitable, which is unrelated to single threaded asynchronous code (and this thread is about multithreading specifically).


    I was asking about comparing the job system to threading with
    Task.Run()
    or Unity's Awaitable, obviously not single threaded asynchronous code.

    Exactly, the job system introduces a lot of complexity while comparisons to traditional threading are abstract and hard to interpret in reality. C# has had a lot of work put into it to be optimized at a lower level, especially if the developer cares to think about that aspect. It's unclear what the benefit of learning a proprietary and complex system would be.

    The job system is hard to learn, the code is harder to read, and the benefits are not concrete compared to traditional threading.

    My intuition is that it would be faster for very small jobs due to the overhead of assigning Tasks to threads in the .NET thread pool, but even that is not much to go on without concrete code and real timing tests.

    You're again comparing multithreaded jobs to single threaded async code, which is not relevant.
     
  30. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,584
    o_O

    I feel like you live in backwards land and are reading all of our posts in a mirror...
     
  31. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,553
    Yeah... none of that is even remotely accurate. It's concrete benefits are that it handles the complex aspects of creating threaded code (eg race condition detection and prevention) and that it's way faster thanks to the Burst compiler (though this is optional if you have reasons you don't want to use it).

    It's not at all difficult to learn. You define the inputs, you define the outputs, you feed it inputs, you schedule the job to run, and then you retrieve the outputs once it's done. If you can't handle that you shouldn't be using threads.

    If you have dependencies you take care of them simply by passing handles to other jobs when you schedule a job.

    https://docs.unity3d.com/Manual/JobSystemCreatingJobs.html
     
  32. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    197
  33. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,553
    I don't see anyone saying that it is. With that said though I don't consider those benchmarks to be all that relevant as they're synthetic not real world scenarios. A real world scenario has branching logic not just large loops. In fact I suspect the reason that .NET lost in the SieveOfEratosthenes benchmark was because of branching logic.

    I have a similar beef with hardware reviewers who benchmark with Cinebench, see a 10% higher score with one processor, and then conclude that it's 10% faster in applications ignoring that there are tasks that are much more efficient than other tasks and thus in the real world it won't be a simple 10%.

    On the topic of .NET 8 keep in mind that Unity is in the process of migrating the scripting framework to .NET which means all of the performance advantages that come with it in addition to Burst and IL2CPP. Until then though it's irrelevant because you're not making games with .NET 8. You're making them with Mono which is very slow.
     
    Last edited: Jan 21, 2024
    MaskedMouse, Nad_B and lordofduct like this.