♪ [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] ♪