Getting Started with the Job System in Unity 2019

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to learn how to get started working with the job system in unity 2019 we won't cover what our jobs how they are created and just how much performance we can get by using them let's begin [Music] hello and welcome I'm your code monkey and this channel is all about helping you learn how to make your own games with in-depth tutorials made by a professional indie game developer so if you find the video helpful consider subscribing okay so first of all the jump system is one of the pillars of the new unity dots tech stack if you want to learn more about dots check the link in the description for the video where I go over what it is how it works and what are the benefits essentially dots is composed of the c-sharp chopped system the burst compiler and the entity component system I've also done a video covering how to get served working with ecs so check that out after this video now the goal with the job system is to make it easier for you to write multi-threaded code as you know most processors nowadays have multiple cores this means that you can have several pieces of code working at the same time so for example if you have some pathfinding code using a single thread you would calculate the first path then the second and the third and so on whereas with multi-threaded code you can calculate multiple paths at the same time however multi-threaded code is also very tough to write and potentially very error-prone with many issues that are extremely difficult to debug so the Java system handles all of the behind-the-scenes stuff like creating and managing threads and all you need to do is create jobs and schedule them so let's get started playing with the job system first you need to be using unity 2019 and then in here you go into window and open the package manager as of this recording some packages are still in preview so go to advanced and enable show preview packages here are all the packages and for the c-sharp java system you have in here the jobs package also related to it is the mathematics package this is a math library specific with performance in mind and also up here you have the first compiler which works very closely with the job system and we're going to test it out in the end also related is the collections package which allows us to use native collections like for example native listen a VAR a which we're going to use in order to enable parallel jumps and the other package related to dots is the entity component system which is right here on the entities package but in here and let's keep things simple and the own he jumps alright so that's it for our package setup now let's get writing some code so first let's make a simple script around our code so a new c-sharp script this will be our testing let's make a game object to add our script testing and just drag our script okay okay now in here let's first make a private void update for our testing so in here right boy update and for service let's just write a simple function that won't take some time to compute so in here make a prep boy let's call this a really tough task so this represents a tough task like for example some pathfinding in here and let's just do some code that won't take some time to execute okay so what we're doing is running a certain calculation 50,000 times this will ensure that this test takes enough time so we can analyze our code speed and also this is the math library which again is inside using that unity dot mathematics now here on the update let's call this function and let's test how long this function won't take to execute three time dog real time since startup holds e number of seconds since the start of the game so we do that and then we calculate it afterwards so here we do the time minus the start time multiplied by a thousand so we get in milliseconds so we should be able to see how long this task won't take to execute let's see okay here we are and here in our console we can see that it's taking around six point six milliseconds all right so now that we have our symbol testing code and let's convert this into a jump now in order to make a job we need to make a struct struct are different from classes in several ways and the main thing is how a struct is a value type where as a class is a reference type that means that when working with structs we are working with a copy rather than a reference so we have our struct and in order to make it a job we implement the eye job interface which is up here on the using unity jobs okay here we need to implement the execute function and now inside this function we have the code that will be executed by the jump so in this case let's just copy our really tough task and in here you can add whatever fields that job requires so for example if we required a public float for something we would add it in here but in this case we don't need any extra fields okay so now that we have our job defined we need to create and schedule it so we go up here let's make a function that won't do the same but as a job so a prep boy call it's really tough task job and in here we create a new really tough job then we have our job instance and in here we just need to go into job and call schedule this tells the job system to schedule this job to be completed by an available thread when possible then this function returns a job handle which is very important to keep track so let's return the jump handle from this function return a job Hemmer okay so we're doing is creating a new job which is a struct and we schedule it on the job system now in order to execute our job let's go here on our update we call the really tough task job which returns a job handle and then in order to tell the job system to complete our job we call job handle dot complete calling this essentially pauses the main thread until the job has been completed and then the main thread continues okay so that's it for making a very simple job you define a struct that contains all the information and the behavior for the job then you create an instance of that struct you schedule the job on the job system and then you tell the job system to complete that particular job so let's see what benefits we get with this function compared to this function so let's had a really easy way of testing both method up here let's add a Syrah nice feel for a private ball use jobs then here we do an if if we want to use jobs then let's do this and if not let's do the normal function all right that should do it let's test okay so here we are and there's our boolean film as you can see we are not using jobs right now and it's not taking the same six point six to seven milliseconds yeah now if we enable jobs and there you go not much really change now the reason for that is because the whole point of multi-threaded code is to do multiple things at once here all we're doing is just a single task so we're waiting for it to be completed on a separate thread so that has gonna take the same amount of time whether it's on the secondary trial or on the main thread so let's do a more appropriate test to see the actual benefits from jobs so this function is meant to represent something really tough like for example some path finding so up here instead of calling it just once let's assume we are calculating the path for 10 different units so in here on our normal code let's just do a 4 so here we are pretending that we are calculating some path finding for 10 different units and let's also do the same thing on our job however in here we need to make sure that first we create all the jobs and only then we tell them to complete otherwise we will be creating a job and then waiting for it to complete before doing the next one and so on so essentially this would be single thread so what we need to do is first define all of them and then complete ulta so in order to do that let's make a collection to hold all of our job handles so in here we do using unity dot collections this allows us to go down here and use a native list let's make a list of job handles in here for our native list we have to pass in the allocator essentially we're telling what we're going to use this list for in this case we're only going to use it to set up our jobs so we can use temp now in here we create our job we get the job handle then we add it to our list so job list and we add our job Himmel and then we don't come complete here but rather out here we go into the job Hemel to call the static function complete all and we passing the list of all of our jobs so the job hand on list so this does the job system to complete all the jobs on this list and when they are done we go back in here onto the main thread and I mean here when using native arrays or native lists we need to make sure to dispose of them so after this we do Chavan a list and we call dispose all right so now we have set up a more appropriate test we're doing multiple tough tasks in the same frame so in the normal way we simply run this function ten times so one after the other and in this way we create ten jobs and then we tell all those jobs to be completed okay let's see the results so here we are and we are now doing ten tasks so as you can see instead of taking six milliseconds we are taking 67 okay now if I enable jobs and there you go all of a sudden it's taking 15 instead of 65 milliseconds so right there you can already see a pretty massive performance boost we can open up the profiler to see exactly what is going on so go into window analysis and open the profile here is our profile let's see what is going on without jobs so disabled jobs and as you can see that's how much more other it is taking so let's pause and look at our frame here we have our main thread as you can see it is taking 70 milliseconds on our testing that update and if you open up the jobs you can see that they are all just idle now let's resume and enable jobs and there you go as you can see it went all the way down and now we can stop and inspect one frame and there you can already see that it's occupying much less time only 15 milliseconds and here you can see all the jobs in how are they being assigned you can see each of them is doing its own task and you can see that several of them are being done at the same time how many worker threads unity creates won't depend on your CPU in my case I have an InTown 6700 K which has 18 logic course so in here you can see 6 threads plus whatever else unity needs so just like this you can already see a pre massive performance boost however there's still another thing which provides another huge boost pretty much for free so let's go back to our code and in here we want to use the burst now in order to do that it's extremely simple which you go here using unity burst and then all we need to go is to our job definition right here and before it we add the attribute burst compound and that's it that's only takes in order to enable burst compound on this job so let's see our code so here we are back on the normal code as you can see taking 67 milliseconds now we enable jobs and there you oh now it's taking 15 milliseconds and now for the last one we go in here into jumps burst and enable and let's see how much it goes down and there you go it goes down from 15 milliseconds down 2.0 3 milliseconds so as you can see that's a massive performance boost by just taking a box obviously how much of a boost you get will depend on what exactly you're doing but as you can see for mathematical functions this is insanely fast alright so this clearly shows you the massive performance you can get by using jobs to run your code in many threads so now that we have covered the basics of our job system let's see what other interfaces we have so what if the interface is a simple I job and then you also have the I job parallel for this is meant when you have a job that you want to execute on elements inside a list so let's create a simple test we're going to create a bunch of units put them in a list and didn't do a job that runs on elements of that list so let's do that so we're going to instantiate a bunch of zombies and put them on that list we're using this class in order to hold our transform and a move Y speed let's create them okay so here we have some code running a thousand times so we're going to instantiate a thousand zombies put them on a random position and then we add them to the list containing a reference to the transform and a random value for the move Y speed so since we're using a serialize film let's go into the editor and here just drag our prefab and here as you can see the prefab it's extremely simple we just have a simple sprite render so let's run the code and see if all of them are being spawned and if there they are we have a thousand zombies being spun okay now we want to make some code in order to move them up and down so let's see back here in our script let's go into the update and for now let's comment out our previous test and here first let's do it the normal way without using jobs so we cycle through all the zombies and here we want to move the transform based on the move Y then if you get to the top of the screen wanting to move down and if the bottom we want them to move up okay so when we reach atop the screen we set it to negative when we reach the bottom set it to positive okay so we should be able to see the zombies going up and down alright so this is some really simple movement code now in order to test our code speed let's also add our really tough tasks in order to make sure that this symbol function takes some time to compete okay so this is our test let's try it out any up here we have our thousand zombies moving up and down and as you can see it is taking around 140 milliseconds per frame all right so now let's see how we can do this much more efficiently using parallel jobs so here in our code let's make a different job so we create a new struct so a public struck and in here instead of implementing a job we implement I job parallel four so here is the definition as you can see we have our execute function that also receives an index so now let's implement that so here we have our same execute function and essentially each time this function is called it will be called on a different index so now let's copy all of our code down there okay copy this now in here you can obviously already own these errors essentially we need to pass in the data that the job will need to execute and since jobs don't work on the main thread we cannot use the transforms directly so it's here that we need to think of exactly what data are we modifying and the answer is we are modifying a position and a move 1 so we go up here and make a public quote for our move line and then we create a public float 3 for our position now the flow 3 is from the super efficient mathematics class which is pretty much just a vector 3 here you can inspect the definition and as you can see we have an X a Y and a Z ok so let's use these two variables in our code so here we are using the position and the move line however remember that this is a parallel job the goal is for this job to run on a list of items that is why we have our index in here so instead of receive a single position and a single move why we want to get an array so in here we received a native array of flow three this will be our position all right and also a negative array of floats for our move wine array and now here in the execute we can use our index and just like that so every time these execute with the different index we're going to modify a different position and our array now one more issue that we have in here is you have to remember that jobs do not get run on the main thread so that means we cannot access in or only entity component so in here we cannot access time down to time inside this job that means we have to pass it in when we create the job so in here we make it only code for the down time and this is the one that we're going to use okay so this is our simple parallel job created now we need to create scheduled and completed so let's go up here into our update and first let's do the same thing using an if in order to run our Java bytecode or normal code and now here let's first create our job and here we fill in our job values so for example the Delta time since this code is being run on the main thread in here we passing the time down the time and then we need to pass in the position array so let's create it up here so we are creating a position array in the move array using a native array and on the applicator we're using temp job since we're going to be using these inside a job and now obviously since we are instantiating an array it is completely empty so we need to fill it up with our current data so we cycle through all of our zombies and we pass in the position array with our transform position and move white with our move one so now with those arrays being filmed we can now pass them into our job and just like that our job now has all of its fields so we can now go into the really tough parallel job and call our schedule now in here you can see we need to pass in our array length which in this case it's our zombie let's not count and then we also need to know the size of each job badge so this value is going to depend a lot on what you're trying to do essentially this is how many indexes each job won't be responsible for so in our case we're trying to run our job on a thousand zombies so let's end each job to handle a hundred ok so just like our normal job this returns a job handle so we have our job handle and just like the normal job we can't dot complete so this won't pause the main thread until our parallel jobs has been completed and that's it for our jump we create our arrays we fill it up with whatever variables we need we create a job instance we schedule our job and we tell it to complete now since we are executing our job on a duplicate piece of data that means that after the work has been done we need to update our original values so we do another cycle so after the families have been calculated we update them on our original transform and the original move one now if you are using this sort of code in a specific part of your game you would just create these native arrays once and then use them every time instead of in Senshi ating a new one every time however for this simple example let's stick with this simple approach so we create our temporary arrays and then we update our original values now again since these are native arrays we need to make sure to dispose of them so in here we call the position array call these foes and same thing on the move one all right that should do it so here we have our normal code and here we have our Java byte code it's not that we've done all of this now we can finally view all of our okay so here we are with our thousand zombies just moving around and as you can see 140 milliseconds now let's enable jobs and there you go it goes from 140 milliseconds to just 40 milliseconds so if we disable jobs our scene is running at 7 frames per second enable jobs and our scene is running at 25 frames per second so just like that you can see a massive performance boost on moving a thousand entities independent from one another so again like we didn't let's look at the profile so here we have running our code without the job system let's inspect one and as you can see we have just our testing that update and all of our worker threads are completely I don't and now lets enable jobs and here is you can see we have our jobs being invoked on our multiple threads so you can already see some massive performance boost now again let's check out the magical burst compound in order to enable it it's very simple we just need to add our attribute to our job so here we have our scene running our normal code we're taking 140 milliseconds okay now enable jobs and there you go go down to 140 down to 40 milliseconds per frame and now enable the first compound let's go here burst enable and let's see goes from 40 and here you go down to 4 milliseconds so without jobs 140 milliseconds with jobs plus the burst compiler and it's 4 milliseconds so just like that you can see the insane improvements you can get from the job system and the burst component now one last thing let's look at parallel jobs specifically for transforms so for that first we go up here and make sure that we are using Unity engine jobs and now we can go down here to make another job our public struct and in here we can implement I job parallel for transform this is a specific parallel job that works on transforms here as you can see we got our execute function which receives an index and also a transform access and the transform axis essentially duplicates a transform but it does so in such a way that we can use it inside our it's now in here and let's do our code the same as on this job except now we no longer need the position right instead we modify this transform directly and now let's see how we create this new type of job in here instead of having this job let's comment this out and here you can see that this function on schedule we require our transform axis right so we need to film that up so instead of using our original position array we are using a transform axis array and filling it up with our references we schedule it using this transform axis array and after a job completes again the transform is automatically updated so we no longer need the position we just need to update our move one all right so that's it as you can see this is a very specific job for working with normal transforms and finally we need to go into the transform access array and call this pose let's also add the birth compound attribute to make it insanely fast ok let's see so again here we have our normal code everything some words the same 140 enable jobs and sound 240 and enable jobs plus the birth compound and we go down to 4 and everything works perfectly fine and we can look at the profiler here we are on the profiler and as you can see our code is running extremely fast the whole thing in just 1 point 3 6 milliseconds everything else is the editor loop and you can see here all the workers and all of them doing all of that transform work in here as you can see we have 16 instances and each of them is taking point 0 to 1 milliseconds so in this video we just covered the C sharp job system you can get even more performance by combining the entire dot stack so the job system with the burst compiler working with the entity component system with all of them put together you can get hundreds of thousands of units on screen so make sure you subscribe the channel for upcoming ECS tutorials using the complete dot stack with the job system and the first compound just to see how much performance we can squeeze out of this new awesome tech stack I hope this video helped you understand how to get started making jobs and the massive performance boost you can enjoy as always you can download the project files and utilities from unity code monkey comm if you liked the video subscribe the channel for more unity two terms post any questions you have in the comments and I'll do my best answer alright see you next time
Info
Channel: Code Monkey
Views: 107,277
Rating: 4.9723725 out of 5
Keywords: unity job system, unity job system tutorial, unity ecs, unity jobs, unity job system example, unity ecs tutorial, unity ecs job system tutorial, unity job tutorial, unity job system demo, unity tutorial ecs, ecs unity, unity ecs job system, ecs unity tutorial, unity dots, unity dots ecs, unity 2019, unity 2019.1, code monkey, brackeys, unity tutorial, unity game tutorial, unity tutorial for beginners, unity 3d, unity 3d tutorials, unity tutorial 2d, unity2d, unity3d, unity
Id: C56bbgtPr_w
Channel Id: undefined
Length: 25min 54sec (1554 seconds)
Published: Fri May 10 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.