If you’ve been reading about .NET 8, you may have heard that Dynamic Profile-Guided Optimization (PGO) is now enabled by default.


Sounds fancy, right? Let’s break it down in plain English.

🧐 What is PGO, Anyway?

Imagine you’re teaching a student how to solve math problems. At first, they try many ways. Over time, they learn which steps work best and start solving faster.

That’s exactly what PGO does for your app.

  • Your app runs.
  • The runtime watches which parts of the code are “hot” (used the most).
  • Then, it re-optimizes those hot spots to make them run faster.

🔥 Static vs Dynamic PGO

There are two main flavors:

Type How it Works Example Downsides
Static PGO You collect data once (e.g., profiling during tests). Compiler uses it to optimize ahead of time. Training a runner before the race. If real-world usage is different, optimizations may not fit well.
Dynamic PGO App learns while running. Runtime adjusts optimizations on the fly. Runner adapts pace based on real race conditions. Slight overhead at the start, but pays off quickly.

👉 In .NET 8, Dynamic PGO is the default. That means every app can benefit automatically.

💡 Why Should You Care?

Here’s what you gain without lifting a finger:

  • Up to 20% faster performance (real-world tests).
  • 💾 Smaller binaries (no need to pre-bake all paths).
  • 🎯 More adaptive apps – it optimizes based on how people actually use your app.
  • 🙌 No manual setup – it just works.

🛠 How to Check & Control Dynamic PGO

By default, .NET 8 turns on Dynamic PGO for you.
So in most cases → you don’t need to touch anything.

But sometimes you may want to:

  • Double-check it’s active.
  • Manually enable/disable for experiments.
  • Compare performance with and without it.

Here’s how 👇

1. Using Project Settings (csproj file)

Every .NET project has a .csproj file (for example MyApp.csproj).
Inside this file, there’s a <PropertyGroup> section where you can add settings.

To enable PGO explicitly:

				
					<PropertyGroup>
  <TieredCompilation>true</TieredCompilation>
  <TieredPGO>true</TieredPGO>
</PropertyGroup>
				
			

What this means:

  • TieredCompilation → allows .NET to recompile methods at runtime (faster startup + better performance later).
  • TieredPGO → tells .NET to use profile-guided optimization dynamically.

👉 If you already have .NET 8, both are usually true by default. But setting them makes sure.

2. Using Environment Variables

You can also control it without touching project files.
This is handy if you just want to test something quickly.

On Windows (PowerShell):
				
					$env:DOTNET_TieredPGO=1
dotnet run
				
			
On Linux / macOS (Bash):
				
					export DOTNET_TieredPGO=1
dotnet run
				
			

Values you can use:

  • 1 → Enable Dynamic PGO
  • 0 → Disable Dynamic PGO

👉 This only applies to the current session/command window. Once you close it, the setting is gone.

3. How Do You Know It’s Working?

Dynamic PGO isn’t something you can “see” directly, but here’s how you can check:

  • Run your app with and without DOTNET_TieredPGO=1.
  • Measure performance (execution time, throughput, benchmarks).
  • You should notice smoother performance, especially in long-running apps like web servers.

For example, an ASP.NET Core app might handle 10–20% more requests per second with PGO enabled.

In short:

  • By default, it’s already ON in .NET 8.
  • Use csproj settings if you want it permanent.
  • Use environment variables if you want to test/experiment.

🧪 Demo: Measuring Dynamic PGO in Action

We’ll create a console app that:

  1. Runs a CPU-heavy calculation (like summing prime numbers).
  2. Measures execution time.
  3. Lets us compare runs with and without Dynamic PGO.

1. Create a New Console Project

Open your terminal and run:

				
					dotnet new console -n PgoDemo
cd PgoDemo
				
			

2. Add Benchmark Code

Replace Program.cs with this:

				
					using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        Console.WriteLine("Dynamic PGO Demo 🚀");
        Console.WriteLine("Running calculation...");

        var stopwatch = Stopwatch.StartNew();

        long result = CalculatePrimes(2_000_000);

        stopwatch.Stop();

        Console.WriteLine($"Result: {result}");
        Console.WriteLine($"Time taken: {stopwatch.ElapsedMilliseconds} ms");
    }

    static long CalculatePrimes(int limit)
    {
        long sum = 0;
        for (int number = 2; number < limit; number++)
        {
            if (IsPrime(number))
                sum += number;
        }
        return sum;
    }

    static bool IsPrime(int n)
    {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; i++)
        {
            if (n % i == 0) return false;
        }
        return true;
    }
}
				
			

👉 This code:

  • Finds all prime numbers up to 2 million.
  • Sums them.
  • Measures how long it takes.

Perfect for stressing the CPU.

3. Run Without PGO

Disable Dynamic PGO:

Windows (PowerShell)
				
					$env:DOTNET_TieredPGO=0
dotnet run -c Release
				
			
Linux/macOS (Bash)
				
					export DOTNET_TieredPGO=0
dotnet run -c Release
				
			

⏱ Note the time taken.

4. Run With PGO

Enable Dynamic PGO:

Windows (PowerShell)
				
					$env:DOTNET_TieredPGO=1
dotnet run -c Release
				
			
Linux/macOS (Bash)
				
					export DOTNET_TieredPGO=1
dotnet run -c Release
				
			

⏱ Compare the time again.

5. What You’ll Notice

  1. First run may not be much faster.
  2. After a few runs, PGO learns hot paths (like IsPrime) and re-optimizes.
  3. On average → 10–20% speedup.

 

📊 Example Results (may vary by machine)

Run Mode Time Taken (ms)
Without PGO 4200 ms
With Dynamic PGO 3400 ms

👉 That’s ~19% faster. And you didn’t change the code at all.

🎤 Final Thoughts

Dynamic PGO is like giving your app a smart brain. It learns, adapts, and runs smoother the more it’s used.

The best part? You don’t need to do anything extra. Just upgrade to .NET 8 and enjoy free performance gains.

So, whether you’re building APIs, crunching data, or running background services, Dynamic PGO makes sure your app doesn’t just run… it runs smarter.

Share This Article

Leave a Comment