Unity Profiler for Beginners: Find What Is Making Your Game Slow

Illustration of a performance graph with spikes under a magnifying glass

Written by

in

“My game is slow” isn’t something you can fix. “EnemyAI.Update takes 6 ms because it calls FindObjectsByType every frame” is. The Unity Profiler is how you get from the first sentence to the second, and it’s already built into the editor. This beginner guide shows how to open it, read the CPU module, find the real bottleneck, profile a build instead of the Editor, spot garbage collection spikes, and mark up your own code so it shows up clearly. No prior profiling experience needed.

Opening the Profiler

Open Window > Analysis > Profiler (or Ctrl+7 / Cmd+7). Make sure the record button (the red circle) is on, then enter Play mode. The graphs start scrolling, with one column per frame.

Play until the slowdown happens, then pause the game (or click in the graph, which pauses recording on that frame). Click a tall spike in the chart. The bottom half of the window now shows exactly what happened during that single frame.

A useful habit: turn on the Stats overlay in the Game view to see the frame rate, and know your target first. At 60 FPS you have 16.7 ms per frame. At 30 FPS, 33.3 ms. Our frame-time budget guide explains how to split that time up.

The Modules at the Top

Each row at the top of the Profiler is a module. The main ones:

  • CPU Usage: time spent per frame, split into categories (scripts, rendering, physics, animation, GC, VSync). Start here almost every time.
  • GPU Usage: GPU time per frame. Not available on every platform or graphics API. Enable it from the Profiler Modules dropdown if it’s missing.
  • Rendering: batches, draw calls (SetPass calls), triangles and vertices.
  • Memory: total memory, textures, meshes, and GC allocations per frame.
  • Physics, Audio, UI: detail for those systems.

Use the Profiler Modules dropdown to hide modules you don’t need. Fewer modules means less profiler overhead.

Reading the CPU Module

Select a frame, then look at the detail panel below. The dropdown at its top-left switches between views:

Hierarchy view

A sortable tree of everything that ran in the frame. The important columns:

Column Meaning
Total Percentage of the frame spent in this item including its children
Self Percentage spent in this item itself, excluding children
Calls How many times it ran this frame
GC Alloc Managed memory allocated this frame
Time ms / Self ms The same as Total / Self, in milliseconds

The workflow: sort by Time ms, expand the biggest item (usually PlayerLoop), and keep expanding the biggest child until you reach something you recognize: Update.ScriptRunBehaviourUpdate leads to BehaviourUpdate and then to your scripts, like EnemyAI.Update(). Then sort by Self ms to find the items that are expensive on their own rather than just containing expensive children.

Watch the Calls column. EnemyAI.Update at 0.02 ms per call is nothing, but with 500 calls it’s 10 ms. The fix might be fewer calls (one manager updating all enemies), not faster code.

Timeline view

Shows the same frame as horizontal bars on a time axis, per thread: main thread, render thread and job worker threads. Use it to see when things happen and whether the main thread is waiting on something else. It makes the CPU-bound versus GPU-bound question below easy to answer.

Deep Profile

By default, the Profiler shows Unity’s callbacks (Update, FixedUpdate) but not every method they call. Deep Profile (toolbar button) records every C# method call. It’s great for finding which helper method is slow, but it adds a lot of overhead and makes everything look slower than it is. Turn it on only to drill into a specific problem, then turn it off. Custom markers (below) are usually the better option.

CPU-Bound or GPU-Bound?

Before optimizing anything, find out which processor is the bottleneck. Optimizing scripts does nothing if the GPU is the limit, and vice versa. Look at what the main thread is waiting on in the CPU module:

  • Gfx.WaitForPresentOnGfxThread or Gfx.WaitForGfxCommandsFromMainThread taking a big chunk of the frame: the CPU is waiting for the GPU or render thread. You’re likely GPU-bound. Look at resolution, overdraw, shaders, post-processing, shadows and draw calls.
  • WaitForTargetFPS: the frame finished early and Unity is idling to hit a target frame rate or VSync. That’s spare time, not a problem.
  • Scripts, physics or animation taking most of the frame: you’re CPU-bound. Work on the biggest items in the Hierarchy.

A quick GPU test: lower the resolution a lot (or the URP Render Scale). If the frame rate jumps, you’re GPU-bound. If it doesn’t change, you’re CPU-bound.

📊 What should each system cost?

The Profiler tells you where time goes. Our free calculator tells you how much time each system should get at 30, 60 or 120 FPS, so you know when a number is actually a problem.

Open the Performance Budget Calculator →

Finding Garbage Collection Spikes

Occasional big spikes on an otherwise smooth graph are often the garbage collector reclaiming memory. In the Hierarchy you’ll see GC.Collect or GC.Alloc entries during those frames.

To find the cause, sort a normal frame by the GC Alloc column. Anything allocating every frame will eventually trigger a collection. Common culprits:

  • String building in Update: scoreText.text = "Score: " + score; every frame. Only update text when the value changes.
  • GetComponent calls and LINQ queries in Update. Cache the results.
  • new List<T>() or arrays every frame. Reuse one list and call Clear().
  • Instantiate and Destroy for bullets and effects. Use object pooling.
  • Unity APIs that return new arrays, like Physics.RaycastAll. Use the NonAlloc versions.

Unity 6 uses the incremental garbage collector by default (Player Settings), which spreads collection over several frames and softens spikes. It helps, but reducing allocations is still the real fix.

Profiling a Real Build

The Editor is not your game. Profiling in Play mode includes Editor overhead (you’ll see EditorLoop taking time), runs your scripts with debugging enabled, and uses your development PC. Numbers from the Editor are fine for spotting obvious problems but can be badly misleading.

To profile a build:

  1. In File > Build Profiles, tick Development Build and Autoconnect Profiler.
  2. Build and run. For mobile, keep the device on the same network as your computer, or connected by USB.
  3. In the Profiler window, the target dropdown (next to the record button, usually saying “Play Mode”) lists running players. Select your build.

Always profile on your lowest target device. A game running at 200 FPS on your desktop can run at 25 on a mid-range phone.

Adding Your Own Markers

To see exactly how long a piece of your code takes without Deep Profile, wrap it in a ProfilerMarker. It has almost no cost, and it’s automatically compiled out of non-development builds.

using Unity.Profiling;
using UnityEngine;

public class EnemyManager : MonoBehaviour
{
    static readonly ProfilerMarker s_PathMarker   = new ProfilerMarker("Enemies.Pathfinding");
    static readonly ProfilerMarker s_TargetMarker = new ProfilerMarker("Enemies.FindTargets");

    void Update()
    {
        using (s_TargetMarker.Auto())
        {
            FindTargets();
        }

        using (s_PathMarker.Auto())
        {
            UpdatePaths();
        }
    }

    void FindTargets() { /* ... */ }
    void UpdatePaths() { /* ... */ }
}

“Enemies.Pathfinding” and “Enemies.FindTargets” now appear as their own entries in the Hierarchy and Timeline views. Use the Hierarchy’s search box to find them quickly.

Related Tools

  • Frame Debugger (Window > Analysis > Frame Debugger): steps through every draw call in a frame. Use it when you’re GPU-bound or have too many batches.
  • Memory Profiler package: takes full memory snapshots and shows which textures, meshes and objects use memory, and compares two snapshots to find leaks.
  • Profile Analyzer package: compares hundreds of frames or two captures (before and after an optimization) statistically, instead of eyeballing single frames.

Troubleshooting

The Profiler shows nothing or stays empty

Make sure recording is on (red circle), the target dropdown says Play Mode (or your build), and you’re in Play mode. Check that the CPU Usage module isn’t hidden in the Modules dropdown.

The build doesn’t appear in the target list

It must be a Development Build. Firewalls often block the connection. Allow Unity through the firewall, or enter the device IP with Direct Connection in the target dropdown. For Android, USB with adb is the most reliable.

EditorLoop takes most of the frame

That’s the Editor, not your game: Inspector repaints, Scene view rendering and so on. Close the Scene view, collapse the Inspector, or better, profile a build.

Everything is slower with the Profiler open

The Profiler has overhead, especially with Deep Profile and many modules. Compare relative numbers (this is 40% of the frame) rather than absolute ones, and turn off Deep Profile.

I can’t find my script in the Hierarchy

Use the search box, or look under PlayerLoop > Update.ScriptRunBehaviourUpdate > BehaviourUpdate. Only methods Unity calls directly appear without Deep Profile. Add a ProfilerMarker for anything else.

FAQ

How do I open the Profiler in Unity?

Window > Analysis > Profiler, or Ctrl+7 (Cmd+7 on Mac). Enter Play mode with recording on to capture frames.

Should I profile in the Editor or in a build?

Use the Editor for quick checks, but trust a Development Build on the target device for real numbers. The Editor adds overhead that doesn’t exist in builds.

What does Gfx.WaitForPresent mean?

The CPU is waiting for the GPU to finish the previous frame. A large value usually means you’re GPU-bound.

What is Deep Profile?

A mode that records every C# method call. It shows more detail but adds heavy overhead, so use it briefly or use ProfilerMarkers instead.

What is GC Alloc in the Profiler?

Managed memory allocated during that frame. Constant allocation leads to garbage collection spikes, so aim for zero GC Alloc in gameplay frames.

Does the Profiler work on mobile?

Yes. Make a Development Build with Autoconnect Profiler and connect over the network or USB from the Profiler’s target dropdown.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *