Introduction to profiling in Unity | Unite Now 2020

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
♪ [MUSIC] ♪ [NARRATOR] Welcome to Unite Now where we bring Unity to you wherever you are. [CIRO] Hey, everyone. Welcome to Unite Now. Today I'm going to give an introduction to profiling in Unity. But first, I want to introduce myself. My name is Ciro Continisio, and I'm the Lead Evangelist for the EMEA region at Unity. So let's take a look at the agenda. The number one thing is I will give a very short intro to profiling, and what profiling means, and what are we looking for when we profile. Then I'm going to jump into the Editor to give you a series of demos on our profiling tools, on the Profiler, on the Frame Debugger, and we're going to briefly touch on something new that we have, which is the Profile Analyzer. Here and there during the talk, but especially at the end, I will give you some profiling and optimization tips, and I will conclude with what the future holds for profiling tools and some improvements that are coming to profiling in 2020.1. And finally, I will give you some resources to read and to watch if you want to keep learning about profiling and really dive deeper. So let's get started. The first thing we want to take a look at is the Profiler. The Profiler in Unity is an instrumentation-based profiler, as opposed to a sample-based profiler. This means that our Profiler captures every little tiny process and function call because it inserts markers at the beginning and at the end of every function call. And this allows us to be very precise. It allows us to see every tiny process that is happening in the game. But it also adds a little bit of overhead. With a sample-based profiler, what will happen instead is that this profiler will take snapshots of the game running, which adds no overhead, but it can miss something. Let's see the Profiler in action in the Unity Editor. To find the Profiler, go to Window > Analysis > Profiler and it will open up here, but it's empty. So to gather some profiling data, let's play the game. As soon as I start the game, the Profiler starts recording profiling data for each and every frame that gets played. And at any point in time, I can click here, and the Profiler will pause the game so I can take a deeper look. And I can scroll through the different frames that have been captured. As you can see, this button here needs to be pressed to be recording, and this drop-down here allows you to select the source of the recording. In this case, I'm looking at the Play mode, but I could enter an IP, which means that you can potentially profile an external device, such as a mobile phone, or a console. Let's take a deeper look at the data now. Let's maximize the view, and you can see that the Profiler is made up of different modules. We have the CPU Usage, the GPU Usage, Memory, Audio, and so forth. And with this drop-down, you can select and enable and disable the ones that you want to use. I'm not going to go over all the modules because each and every one of them requires a different talk. I'm going to focus, for now, on the CPU Usage. If we expand this window, this is the Hierarchy view, and this gives you a Hierarchy view of the different function calls that are being called in this specific frame. So if I move here, you will see that this will change. And for each and every one of them, you have a lot of data, you can dive deeper, and you can really see what is being called at any specific point in time. If you select them, you will notice that there's a bunch of numbers here, there's a percentage which refers to the percentage of the frame that was taken to execute these function calls. You have the number that these calls have been called, and you have some numbers in milliseconds, which refer to the time that was needed to execute these function calls. So which one of these are you actually interested in? Let's take a look. So when we talk about measuring performance, sometimes people say, "My game runs at 20 FPS. What should I do?" They refer to the frames per second. And although frames per second might be the ultimate goal of performance optimization, when you profile, you really want to measure the milliseconds of each operation to get an idea what's going on. So we talk about something which we refer to as the budget. The budget is... the formula would be 1,000 milliseconds divided by the target frames per second that you want to achieve. That gives you your budget per frame. So if you want to achieve 60 fps, your budget is going to be 16 milliseconds to spend on one particular frame. If you're fine with 30 fps, then your budget is going to be 32 milliseconds, so you have more time. And if you want to achieve higher frame rates like 90 fps, in the case of VR, you just have 11 milliseconds to spend. So you really need to take a look at the milliseconds that each function call takes to understand where you can shave off some performance. I want to briefly touch on rendering API calls because this can be really confusing, especially when you're profiling for the first time, and it really confused me as well. People ask, "Why is 70% of my frame time spent on Gfx.WaitForPresent?" What are these mysterious rendering calls? And you have no control over these calls, so that can be really confusing for beginner game developers. To understand these rendering calls, we need to take a look at the relationship between the CPU and the GPU and how a frame is constructed. If you look at the structure of a frame, and you can see there at the top we have the budget for our frame, 16 milliseconds, there's a certain amount of work that the CPU has to do which encompasses many things. The engine code, which you have no control over, Your code, which you wrote, and then there's some rendering code, which happens usually at the end of the frame, which is basically the code that produces all the data that the GPU needs to render the frame. So this code hands off this data to the GPU, prepares some API call for graphic and rendering APIs, and then the GPU can start rendering the frame. When we look at the bigger picture, obviously, you have multiple frames, and the CPU and the GPU are always working in parallel. But you always need this space in between two frames for the CPU to hand off the work to the GPU. Sometimes things don't go very well and the CPU takes longer. Let's say, for example, it takes 21 milliseconds to execute all of the code. And once it's ready to hand off the work to the GPU, as you can see, what happens is that the GPU has been waiting, so the frame rate decreases because the GPU didn't have the data that it needed to render the next frame. And that's represented by that block here at the center of the screen that says Wait. That's the wait on the GPU. We say that in these cases, we are CPU-bound, meaning that the GPU was waiting for the CPU to start working. Other times, we are GPU-bound, meaning that the CPU is actually waiting for the GPU because, as you can see, there at the bottom the big faded green block, the GPU from the previous frame was taking such a long time to render, that the CPU, even though you had the data ready and it was done with all its work, is waiting. This is when, for example, we have Gfx.WaitForPresent. That little block there that says Wait in the center of the screen, that would be Gfx.WaitForPresent because the Profiler is showing you that the CPU is waiting for presenting. It's waiting for the GPU to have finished the work from the previous frame to produce the data that it needs to render the next frame. With this knowledge, we can go back to the Unity Editor and try to make sense of the data. In here, we can already see some familiar faces, like the rendering code that I mentioned before. But these numbers, they still don't make a lot of sense when taken out of context. So we can go from Hierarchy view to Timeline view, and in here we can see a full overview, a visual overview of the frame, and see each function how long it's taking, and where is it in time. The interesting thing here you will see is that the EditorLoop is eating a lot of the time of our frame, and this is normal because the Unity Editor takes some time to execute, and this is also fine because players won't play the game in the Unity Editor. So this is not something that will affect them. But it can make profiling confusing. So for this reason, one of the best suggestions that I can give you is to profile the build instead of the Editor. To do that, we need to create a build, so we go into Build Settings, and we want to make sure that Development Build is checked. We can also check Autoconnect Profiler, so as soon as we start the build, the Profiler is going to connect to it. And then a little suggestion is you can go to the Project Settings and make sure that Run in Background is unchecked. So whenever I switch back to the Editor, the game stops and I can analyze the data. I've already created a build, so what I need to do is just to go to this drop-down and make sure that I'm connected to the build by selecting this, and then I can switch to it, play the game, and capture some profiling data. Once I switch back to the Unity Editor, the build is going to stop because it doesn't run in the background, and I can now analyze this new data. And as you can see, now it makes a lot of sense. You can see that the PlayerLoop has a certain length, and it's taking the whole duration of the frame. I'm just past the 16 milliseconds mark, so I'm 60 frames per second. You can also see some of those familiar functions we mentioned before, mysterious functions that now are familiar. Gfx.WaitForPresentOnGfxThread, this is showing me that the CPU here is waiting for the Render Thread, which is doing something else. And then it can execute some rendering code. And here we're waiting for the Target FPS. So the game has a set fps number, and the CPU is, again, waiting for that. Now everything is more clear. So now we better understand the relationship between CPU and GPU, and we can also place in time, and what do they do, those graphic API codes. Another important thing to remember is that the frame rate, you can actually set it, you can target an ideal frame rate, and you can ask Unity to maintain that, and it's a cap, it's a maximum. You can do that using an API called Application.targetFrameRate. And by default, if you don't do that, the frame rate is as fast as possible on desktop platforms, but it's capped already at 30 fps on mobile devices for performance reasons. But you can unlock it and bring it to 60 fps, for example. Then it depends if your game can maintain that frame rate. For more information, you can check the Unity Manual at the URL shown on screen. So now you better understand all the rendering API calls, and you can make sense of them. Again, in the Manual, you will find more information about each and every one of them explained in detail at that URL. Before we move on, I want to touch on one final thing regarding graphics, which is screen tearing and VSync. So screen tearing is a visual artifact that appears when multiple frames are shown in one screen refresh. And I'm gonna explain better what it means. It usually happens when objects move on screen horizontally, and it only happens when the game is running. Do you see it in the image? Let me show you better. You see it's just there, it's cutting the screen in half, and it's a very nasty artifact. VSync is basically the cure. It's vertical synchronization. It means that it synchronizes the graphic card with the refresh rate of the monitor, ensuring that the screen displays only entire frames. But what is actually screen tearing? Let me explain. The monitor has a certain refresh rate. Usually it's expressed in hertz, which means the time that refreshes per second. So again, 60 milliseconds is the interval. Let's say that our GPU takes 12 milliseconds to render. That's optimal, we're under the budget, so we render every single frame, and we hit 60 frames per second. When VSync is enabled, it requires us to render every frame, and it will not render half frames, so it will not render if the GPU is done in between those refresh rates. This means that if we hit 12 milliseconds, we are able to feed a frame every time the monitor has to refresh. So, for example, the first time we feed one frame, and then each time the monitor refreshes, it always finds a new frame that the GPU has just produced. That's the perfect situation, and the game achieves 60 fps. Rock solid. But if our frame takes longer to render, let's say, 17 milliseconds, so we're above budget, then what happens is that the first time we hit the frame rate, and we provide one frame to the monitor, the monitor refreshes. But because VSync is active, the next time, because we don't hit the budget the monitor has to reuse the previous frame. So it shows the same frame again, and this means that we just hit one every out of two refreshes with the frames that the GPU is producing. Basically, even though we're almost hitting 60 frames per second, because of VSync, the game is effectively displayed at 30 fps because it's only using one every two frames that the GPU has produced. You can enable or disable VSync for your builds from Project Settings > Quality. Or in the Editor, you can toggle VSync on and off by going to the drop-down in the Game view and check VSync on the drop-down list. And the Scene view has no VSync. One thing to note is that on mobile phones, VSync is forced on at the hardware level. This means that if you turn off VSync in Unity, this has no effect. And this is basically my tip, is the fact that when you work with VSync you really need to understand what it means, and especially when you're on mobile in your profiling, you might find it confusing because you're expecting it not to be on, but it's actually on at the hardware level. So you're seeing that your frame, your CPU and GPU are, even though they run fast enough, maybe they're hitting 45 frames per second. Because VSync is on, you're brought down to 30 fps. That can be really confusing, so keep that in mind. If you don't hit 60 frames per second, you'll be brought down to 30. If you don't hit 30, you'll be brought down to 15, and so on. So every time VSync is on, you need to really make sure that you hit that frame budget. Now that we have the complete picture of what happens during the frame, we can go back to the Unity Editor, and we can really explore the Profiler view on the timeline. Now that we have the complete picture of how a frame gets rendered, we can really go back to the Editor and explore the Timeline view of the Profiler. As you can see here, we have the PlayerLoop, which fills the entire frame, and this is the CPU, this is the work that the CPU is doing. We now recognize WaitForTargetFPS, which is basically the time that the CPU is waiting for the next frame to start because it has finished working but we have VSync on, so we need to wait. We can also expand this, and we see the full extent of all the functions that get called until the very last one. And here, we see another piece of the puzzle that we touched on before: Gfx.WaitForPresentOnGfxThread. This is basically telling us that the Main Thread of the CPU is waiting for another thread. So now, again, we can keep exploring, and thanks to the Timeline view, we can see the full extent of what the CPU is doing on different threads. The Main Thread, the Render Thread, which is preparing some data for the GPU. As you can see, there's a lot of work that the Render Thread is doing. And we need to make sure that this work finishes soon enough, otherwise, the CPU on the next frame will not be able to continue. So you see here, there's a lot of these WaitForPresentOnGfxThread, WaitForTargetFPS. And then, again, this one in the next frame, which is waiting for the Render Thread to finish working and looks here that it's working on the PostProcessing Effects, which, as you know, are very heavy effects. And then the other important thing here in the Timeline view is this one. We can also inspect what the Jobs are doing. So here we can take a look at the work that is being distributed on the other threads that our CPU supports. In this case, as you can see here, the other threads are a bit underutilized. Here we have a little bit of work on probably rendering stuff. Here we have some work from the Animator. So as you can see, the Animator is a multithreaded Unity component. And here we have some work on... Sorry, let me zoom in. We have some work on Physics. But as you can see this work is very little. The majority of Physics happens on the Main Thread. Unfortunately, Physics are not multithreaded in Unity. So as I was saying, this part here right now is a bit underutilized, but this really comes in handy when you're working with either the Unity C# Job System, or especially when you're working with DOTS and ECS. And with DOTS and ECS, when you use Jobs heavily, here you can really inspect what's going on. And this also gives you an idea of why, for example, some function here is waiting and who is it waiting for. So here with the whole Timeline view, you can really see who's keeping somebody busy. So, for example, in this case. we know that this function is keeping the CPU Main Thread busy. Talking about profiling, there's one thing that we need to mention which is very important, and is garbage collection. Whenever you allocate memory on the heap, so you create a type which is not a primitive type, like Float or Int, but rather, for example, an object or a GameObject, garbage is created, and garbage is basically a memory that needs to be freed by this process called the "garbage collector" on your behalf. In C#, you have no control over when memory gets freed, but rather you have this process which comes in a few frames after and frees up the memory that is not used anymore, that is not referenced anywhere in your program. And this process takes a little bit of CPU to get executed and produces what we usually refer to as spikes. You can see them in the image here, so in the Profiler, they literally look like little spikes. And that means that sometimes you can have these frames which take longer than normal, and your game stutters, basically. And depending on how much garbage you produce, these spikes happen more often and can really drop the frame rate for longer periods of time. So garbage collection has two stages. There's the allocation of the garbage and there's the removal, which is when the Garbage Collector kicks in. And you have limited control over both stages. Let's go to the Editor and see how it looks like. In this situation, I've created a couple of GameObjects. I've attached some Scripts, and I've basically simulated the creation of garbage. If we look at the Scripts, they're very simple. One of them just creates a list of strings, so it allocates this memory 100 times per frame, so very intense. The other one instantiates a new GameObject, which is also an operation which produces garbage. And it does it in the update as well, so very frequently. So I forced the creation of a lot of garbage just for the demonstration of how it looks in the Profiler. Now, in the Profiler, how do we find garbage creation and garbage collection? If we look around, the first thing we can do we can use the search function here. The functions are called GC.Alloc, and as you can see, I already find a few instances here, and obviously every frame I will see the function that is creating the most garbage, which is the one that I've created artificially. You can press F to locate the function in the Hierarchy, and immediately I can see this is the StringCreator function that I made myself. So this is one way to find garbage. Again, you use the search function "gc.alloc" and you can also use "gc.collect" to try and find frames where garbage is being collected. So this is when the garbage collector kicks in and comes in and finds the memory and frees it up. Other ways, you could, for example, use this column here and order things by GC.Alloc, and this way, wherever you are, you will always see the functions that are allocating more garbage first. So in this case, for example, I can open the PlayerLoop, I see that there's some ScriptRunBehaviorUpdates or some Script, in my game, is producing garbage, and quickly I can find the culprit, which is this one StringCreator.Update. As you can see, it calls GC.Alloc 200 times. Another useful feature here in the Profiler is this panel here to the right. You can Show Related Objects to a call. So, for example, I can see that some kind of call here, Instantiator.Update, is being called by this object here. So I can see that this function here refers to this object here. And another opportunity is to show the calls to a certain function. So, for example, I can see that GC.Alloc has been called this many times by these objects in the frame. With this panel here to the right, you can obtain more information on the relationship between who calls who, which object is calling which function. The other way to find spikes could be also using this interface here that allows you to see things in layers, and here you have the garbage collector. And as you can see, as I turn it on, this is not that much garbage that I've produced, but I can already spot where the spikes are. And so I can quickly go and find the frame. And here I will see that there's going to be "gc.collect." So I can see where the function is, and then I can start investigating and seeing who's calling this function. So now I see that, for example, it's my StringCreator.Update, so it's the Script that I put here. So with the combination of all these tools, you can really find where garbage gets allocated and where garbage gets freed, and you can really take a decision. Again, you don't have full control over this process, and in any case, every now and then, some garbage will be allocated regardless. So you can't get rid of all of it, but you just need to make sure that you don't have incredible spikes that can really hurt your performance and really drop the frame rate, even temporarily. So some tips to finish the topic of garbage collection. The first one would be try not to create garbage in the first place. There's so many talks going around about which functions to use and which functions not to use to try and avoid creating garbage. One tip is obviously to keep references to the things that you think you're going to be using many times in your frame rather than just recreating them, or getting a new reference to them. For example, GetComponent. Rather than using GetComponent all the time, just cache reference to that component and reuse it over multiple frames. Since Unity 2019.1, you have some limited control over garbage collection with some APIs, and I suggest you go and check this URL see on screen. It's in the Manual, again. And then recently, we have introduced this new thing which is called the Incremental Garbage Collector. It's in an experimental phase. You can simply enable it from Project Settings > Player. It's a checkbox, you enable it, and basically Unity tries to spread the load of garbage collection over multiple frames. So rather than collecting all the garbage at once, it will launch several, smaller garbage collection processes over multiple frames, and that should impact your game a bit less than usual. So we've seen how to analyze one frame, but a very useful technique when profiling is to compare multiple frames with each other. For that reason, we've created this new tool called the Profile Analyzer, which basically allows you to have a bird's-eye view on profiling data and to compare multiple sets, multiple frames together with each other and draw some conclusions. The Profile Analyzer package was released last year in preview. But let's see it in action in the Editor. This is Unity 2020.1. You can see it here. This is actually an empty project, and this might be surprising to you but the idea here is that I'm gonna pull some data in from the game that I've built before that I've shown you before in 2019.3 and analyze it here with the new tools. So to get the tool, obviously, Package Manager, you go to the Package Manager, you download the Profile Analyzer, and you have it in your project. The Profiler Analyzer looks like this. Right now, it's empty, but let's get some data inside the Profiler. So the first thing we can do, for example, I can go in here. I'll show you one thing that I didn't mention before is I can load some data from the previous recordings that I've saved previously and bring it into the Profiler. If you want to save the data, you usually use this button here, so I've done it previously. So this is the data from the game I was analyzing before. This is not Editor data, this is the build. And once I have it in here, I can go to the Profile Analyzer and click Pull Data, and this is going to take data from the Unity Profiler. So I click this, Unity gathers the data from the Profiler into the Analyzer, and now I can look at it from here. So it's analyzing it. And the Profile Analyzer has two modes. It has Single and Compare. Right now, I only have one set of data, so let's stay on Single. And you can see there's a lot of information here, but I want you to focus on the things that you see here at the bottom. So here I have a representation of the frames in time. I can make a selection, for example, I can take this many frames, and once I do that, here at the bottom, I can see a comparison of what's happening over that interval of time. It's not just showing me one frame, but it's showing me how things change and how things are repeated several times over the course of this many frames. So I can look at intervals of time and see if there's something that is recurring, if there's a function that gets called too many times, if there's too much garbage collection, or garbage allocation, and so forth. So you can see here it says: Top 10 markers on median frame. Here I can see basically what are the things that are more present. And obviously, the first one will be PlayerLoop. But then I can dive in and take a look at the ones that are more present over this period of time. So this first view, the Single view allows me to see a single set of data and make conclusions over that. Here you can see the Median Bar. It shows you the median of the time that it took to execute this function over that interval of time, the Count per frame, and stuff like that. But the interesting option here is to Compare. To Compare I can do two things. The first thing is I can look at the set of data that I was just looking at and basically take different samples from different moments in time. So let's say that I have some action that happens here and some other action that happens here, I can compare them and look at how they differ between these two intervals. So I can see that, for example, there's WaitForTargetFPS that happens more in this second set of data and how much more it happens. So I can draw conclusions and realize that there's something here which is calling this function more, why is it so, and so I can go in and maybe go back to the Profiler and look at those frames and investigate. The other interesting thing though, right now, as I said, I have the same set of data. I can load a different set of data here on the second line. To do that I can go to the Profiler, I can pull in, for example, here I have another set of data from when I've been killed in the game and I've respawned. So I will load that. As you can see, it looks quite different because I was playing the game for a while, and my computer was getting heated up, and the CPU was throttling, so it was basically being held back by the OS not to heat up the laptop. So the performance is totally different, the frame rate is a bit lower, and now I can go in the Profile Analyzer and pull that second set of data into the second line. And once it's done, Unity will allow me to compare them, and I can even pair the selection here. Now it's loading, so it's analyzing. And as you can see, it analyzes all the threads as well. But one thing I could do is pair the selection, and I could say I want these frames here because let's assume that the selection starts when the game starts. So I've profiled the game. I've played the game for a certain amount of time. Then I made some change in the code because I want to optimize. And then I profile the game again in the same situation for the same amount of time. So now I want to see those two sets of frames, which is basically when I reached, for example, some checkpoint, I want to see how they compare in time before and after the fixing code that I've made. And now I can really analyze, and I can see that, for example, in this second set of data, as I said before, my frame rate was lower, so the PlayerLoop takes longer. Here you can actually select a scale for this vertical axis and see how the two sets of frames are basically comparing against the budget. Let's say that the budget is 66 milliseconds, 33, as you can see here, we have 60 frames per second, 30 and 15. So here I can see that, for example, here, I wasn't even reaching 30 sometimes. And again, now I can really make a selection and use the Profiler Analyzer to see how these two sets of data compare against each other, and I can really understand what my fixes encode, what changes did they produce, and whether they were good or bad. Maybe I want to roll them back. So the Profile Analyzer gives you a holistic view over all these data. Let's leave the CPU aside for a second, and let's focus on the GPU. To profile the rendering, we have one tool in Unity which could be really useful which is called Frame Debugger. There's one thing to say, though, the Frame Debugger just gives you a window on what the GPU is doing and it shows you a lot of information over how Unity prepares the data to be rendered. But it doesn't show you what the GPU is doing on its side. So you only have a limited view, limited information on what is happening on the rendering side. And that's because of the separation between the two pieces of hardware. Let's see it in the Editor. So the Frame Debugger, you will find it as usual under Window > Analysis > Frame Debugger. It opens up like this. It shows an empty interface, and you need to enable it to basically ask Unity to analyze the frame and show you all of the different parts of the rendering and what they do. As you can see, as I was saying before, there's not really profiling data in here, it doesn't tell you how long each function call took. For instance, if I select anything really, like Draw Mesh, there's no information about the timing that it took, Unity doesn't really know. Unity just knows that this thing is going to happen on the graphic card at some point. And it gives you some very interesting information over the ShaderProperties, so the properties that the shader that is producing this particular draw call is utilizing. And you can really analyze the RenderLoop. So if you look here, Render Camera, you can really see the different phases of the rendering happen one by one and you can dive into them. On here, you actually have a slider that allows you to see what the stages are, and you can really go all the way back from where Unity produces the Shadow map, the depth, and then all the way down to the rendering of individual Meshes. For example, let's focus on something like these barrels here. I can see that they are the same object, but they use different draw calls. And I find them here, so I see here Draw Mesh BreakableBarrel. They are really the same Mesh and the same material, so why are they using different draw calls? Well, the Frame Debugger gives you this information, which could be useful. So if I select from the second barrel on, I will see here that it says, why this draw call can't be batched with the previous one. Well, the material doesn't have GPU instancing enabled. So I can now select one of these objects, which, as you can see, get pinked in the Hierarchy. And go to the Inspector, select the material they're using, and Enable GPU Instancing. As soon as I do, the draw calls get batched together. Now, I need to find it again. Here they are. And as you can see, now the special barrels get drawn together, all four of them. So I've basically saved three draw calls. So as you can see, I'm not really profiling, I'm more like inspecting the frame. That's what the Frame Debugger does. Other interesting bits, for example, if I now disable the Frame Debugger and I play the game, and, for example, I smash one of these barrels, and I've destroyed it, and now I enable the Frame Debugger again, and I want to see what's happening here, before I noticed that these barrels, for example, here you see each one of those pieces that I smashed are a different draw call. And this is not the best here. I mean, there's only two barrels, so that's fine. This is not a game where you have hundreds of objects on screen of the same type at the same time. But, for instance, here I could see that these can't be batched in one draw call because there's different Meshes. So now I can go in here, and I can find that actually this object, even though they look similar, all the pieces that I smashed, if I go to the source FBX, I can now see that all the planks are different Meshes. So there's some optimization that could be done here. For instance, I could really connect all of the different pieces to the same Mesh, and then they would be drawn in the same draw call, and I will save all of these, they will become just one. So as you can see, the Frame Debugger gives you a window on what's happening on the GPU and allows you to create occasions to improve the performance. It doesn't really give you a reading on the performance itself, but it gives you a hint of what might be wrong in your rendering. So after this long tour of all the profiling and analysis tools that we have in Unity, let's take a look at some final profiling tips. The first thing I want to touch on is when to profile. Some people think that they can profile at any point in time. So they are working on the game, and then in the evening, they just profile a little bit and try to optimize the game. Well, my suggestion is profiling is a time-consuming process. It should be a time-consuming process if you do it correctly. So don't do it randomly, don't do it at random point in time but really profile at key points during development. So, for example, you introduce some new feature, or you introduce a new character, or you're developing a new area in the game, and my suggestion is try to allocate some time to do profiling properly, and note down all the data you find. Don't just go by memory and think, "Oh well, last time I profiled, my frames were taking, on average, 16 milliseconds, so I'm 60 frames per second, and now I'm 55. And the point is like, okay, but why? Why did you lose some performance? Where did you lose performance? So maybe use the Save function in the Profiler and name correctly all the files that you save with maybe the date when you profiled and the stage where you were. You can associate them to a certain commit on Git. So the next time you profile, you really have an understanding of how the game was performing last time you profiled, and you can compare them maybe with the Profile Analyzer. So profile at key points during development, then you fix things, and then you repeat, you profile again, and you see if the fix you produced actually improved the performance, or maybe you want to roll it back. Again, the Profile Analyzer here would be a really useful tool to compare the profiling before and after. And obviously, I'm not saying that you should postpone the profiling until just before shipping, you need to do it at key points in time, early enough to make an impact on your development. And also, profile real-world scenarios. So profile the game that players will play, not the game that you are developing. Some things to keep in mind: don't profile things in isolation, so don't profile a system in a Scene which is not the final Scene where that system will run. That doesn't really make sense, you need to profile things all together, you need to see if your system is taking how much time, alongside other systems, not in isolation. Also, profile on a device. We've seen it before with the build, we created the build, so rather than profiling in the Editor, you just create a build on the PC or the device that you're using and you profile that, not the Editor itself, because that will give you some fake profiling data. Remember that you can connect the Editor to any external device through their IP, if they're on the same Wi-Fi network. Then profile different parts of your game. You want to basically take a look at the first level, at the third level, Even though they're running the same code, there might be differences. Maybe the rendering is killing it at that point, so your game is GPU-bound. So even if your CPU is doing great and your code is very efficient, there might be something in the organization of the Scene, or maybe in the usage of post effects that is killing the performance. And then the final tip, which is very important for both computers but also especially for mobile, is to profile in sustained mode, which means that you want to really take a look at the performance, not in a perfect scenario when you just started the game. But you want to keep playing for a while and let the CPU heat up and get warm for a while, and then see if the performance is the same after a while, because, especially on device, you have this behavior called throttling, which means that the system itself will basically down-power the CPU after a while to avoid it from getting too warm. So basically your game, which was running fine before, now runs at half the speed. And this is something to keep in mind because players will play for a long period of time, so you need to make sure that your game runs decently enough for a sustained amount of time. There are many techniques on this, you can save some performance earlier, so letting the game run slightly slower to not heat the device too much and save some performance for later. We have another package, which is called Adaptive Performance, and that's really useful for, basically, scaling the game's capability in time in response to how the device is behaving and how it's throttling the CPU. So something to keep in mind. Another small tip when profiling, and this I've shown you before a little bit while I was showing you the Profile Analyzer, is that you don't need to open the original project to profile a build. So if I have a build and I want to profile it, and, for example, maybe I migrated to a new Unity version, I can totally profile from that new version of the project, even if the build has been created before. Again, because maybe I want to compare with before the migration. So that's doable. You can also load saved profile data in an empty project. I've shown you before, I've done it in 2020.1 when I was showing the Profile Analyzer. I just loaded data created in 2019.3 in a different project. And then finally, you can use this API: Profile.logFile to dump profiling data to a file maybe directly from within a build. So you tie a key of the keyboard, and when you press that key the build will produce some profiling data that you can then analyze later on. Finally, some very quick optimization tips. There are entire talks being done on optimization, so I'm gonna go very quickly over something that I think is very important, maybe a bit basic. Number one, obviously, instantiating and destroying GameObjects has a big cost. So you can use object pooling if you keep creating and destroying a huge number of objects every frame. This is a useful technique, and it avoids garbage collection spikes later on. The second one might sound silly, but nothing is free. So anything that you leave in the game has a cost. When I say leave, I mean maybe you're tempted to just leave something without removing it because you think that it's an empty GameObject, or it's a camera that is not rendering anything. Well, they also have a cost, they have a small impact on the frame. So once you want to produce the final build, you should really take a look at what's left in the Scene, all the tools that are running, and try to shave them off to remove them to shave some performance and let the game run faster. Also MonoBehaviour, for example, that are not running an update, they still have a cost on memory. Then this is maybe more important: when you have API calls that are repeated in time, many, many times, maybe several times per frame, you should try to learn to spread the load over multiple frames because usually no API call creates a spike by itself, but by repetition. So, for example, if you have some costly AI calculation, like pathfinding, that you wrote yourself, maybe you can spread that into multiple frames. You don't calculate the whole path, but you start the unit working on the first frame, even though the calculation is not finished completely, and then you finished it over multiple frames. If there's many entities that do the same thing, maybe you can stagger the decisions in time, so every frame you run batches of decisions of, for example, one to five entities, and over multiple frames you complete them all. There will be a small delay, but maybe the player doesn't really realize, if it makes sense for your game. You can create a smarter loading strategy for Prefabs and Scenes. So rather than loading entire Scenes at once, maybe you can break the load in different frames and load smaller Scenes when needed. And now let's take a brief look at some future tools coming to profiling in Unity. In 2020.1, the Profiler will receive some improvements like the Standalone Process Profiler and Flow Events. Let me show them in context. If you go to the 2020.1 version of Unity, you can just go to Window > Analysis, and rather than choosing Profiler you just choose Profiler Standalone Process. Unity tells you that this thing will take a few seconds to start, and what it does is it starts a new process, which just contains profiling tools. So now you have kind of like a Unity version, which as you can see, gives you the Console, or the Profiler, Memory Profiler, or the Profiler Analyzer, and just that, and that has very little impact on the application that maybe is running and you want to profile. So, for instance, now I can connect to this build here, and I can let it run for a few frames. And now if I click here, I'm looking at the lightest profiling experience possible, and I can really look at what the game is doing on my machine, almost in isolation, and I can inspect deeper. And then the other improvement that I briefly mentioned, Flow Events, you can enable it from here, and it's really useful when you work with multi-threading because as you can see, it visualizes, let me make some space for this. Visualizing the flow of events, for example, I can see that here there's this line which tells me, and I press F to focus, it tells me that there's some process running on the Main Thread, for instance, which is spinning up some other process here on the Main Thread, but then this one is spinning up some process on one of the Jobs running on different threads. And you can see how each Job, especially Jobs, they show the connection they have with the Main Thread. So here Physics is doing that, and then here you see some NavMesh Agent works. And then here you have the Animator, which is spinning some jobs. Let me find it here. There we go. ProcessGraphJob is spinning up jobs here on multiple threads at the same time. And you can really investigate on who basically launched this job, where is the function coming from in a very visual way. It's really useful. So you find it here, Show Flow Events in 2020.1. And then finally there's a new tool that we're working on. We've been working on it for a while, as you might know, is the Memory Profiler. It maps out the memory layout of Unity application. It's available in Preview through the Package Manager. The reason why it's still in preview is because we really, really want to make it useful, and there's so many things that we need to take into account still. And also we want to improve the user experience, the UX. So it's still a work in progress, but it's really useful already, so please check it out through the Package Manager. It works also in previous versions of Unity. We're at the end of the talk, so I just want to leave you with some reading and watching material to further learn about profiling and optimization. There's a spectacular guide on performance and optimization on Unity Learn that you will find at this URL. And then there's a bunch of talks you can find from previous Unites, from Copenhagen 2019, one about the Profile Analyzer that I just detailed before. There's one from one of our DREs, Developer Relation Engineer's tales from the optimization trenches. These are like real use cases of optimization from real companies that make different types of games. And then you have other talks completely about squeezing performance and the best practices that you should use when you work in Unity from Ian Dundore, Unite Berlin 2018. And there's so many more, you just need to Google or just go to the Unity YouTube channel and search for optimization or profiling. This is all from me. If you have questions, I encourage you to bring them to the forums. You see the URL there, the Bitly link. Don't forget the capitalization of that link, otherwise, it won't work. But, yeah, please just hit me with questions on the forums, and the profiling team and other developers at Unity will help me in answering them in the best way possible. Thank you so much for watching, and I hope to see you in the next presentation. Bye. ♪ [MUSIC] ♪
Info
Channel: Unity
Views: 30,210
Rating: undefined out of 5
Keywords: Unity3d, Unity, Unity Technologies, Games, Game Development, Game Dev, Game Engine
Id: uXRURWwabF4
Channel Id: undefined
Length: 53min 12sec (3192 seconds)
Published: Sun May 24 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.