Skip to content

Commit 8350329

Browse files
Merge pull request #2934 from jasonrandrews/review2
Tech review for PGO on Windows Learning Path
2 parents c622365 + 5097994 commit 8350329

5 files changed

Lines changed: 115 additions & 92 deletions

File tree

content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/_index.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
11
---
2-
title: Optimize C++ performance with Profile-Guided Optimization and Google Benchmark
2+
title: Optimize C++ applications on Windows on Arm using Profile-Guided Optimization
33

44
draft: true
55
cascade:
66
draft: true
77

8-
minutes_to_complete: 15
8+
minutes_to_complete: 30
99

10-
who_is_this_for: Developers looking to optimize C++ performance on an Arm-based Windows device, based on runtime behavior.
10+
who_is_this_for: This is an introductory topic for software developers who want to optimize C++ application performance on Windows on Arm using profile-guided optimization.
1111

1212
learning_objectives:
1313
- Microbenchmark a function using Google Benchmark.
1414
- Apply profile-guided optimization to build performance-tuned binaries for Windows on Arm.
15+
- Measure and compare performance improvements from PGO-optimized builds.
1516

1617
prerequisites:
1718
- Basic C++ understanding.
18-
- Access to an Arm-based Windows machine.
19+
- A Windows on Arm machine with [Visual Studio](/install-guides/vs-woa/) and the C++ desktop development tools installed.
1920

2021
author: Tom Dunkle
2122

2223
### Tags
2324
skilllevels: Introductory
24-
subjects: ML
25-
armips:
26-
- Neoverse
25+
subjects: Performance and Architecture
2726
tools_software_languages:
27+
- C
28+
- MSVC
2829
- Google Benchmark
29-
- Runbook
30+
- PGO
3031
operatingsystems:
3132
- Windows
33+
armips:
34+
- Cortex-A
3235

3336
further_reading:
3437
- resource:
@@ -39,6 +42,14 @@ further_reading:
3942
title: Google Benchmark Library
4043
link: https://github.com/google/benchmark
4144
type: documentation
45+
- resource:
46+
title: Windows on Arm developer documentation
47+
link: https://learn.microsoft.com/en-us/windows/arm/overview
48+
type: documentation
49+
- resource:
50+
title: Arm Performance optimization resources
51+
link: https://learn.arm.com/learning-paths/laptops-and-desktops/
52+
type: website
4253

4354

4455

content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/how-to-1.md

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,36 @@ weight: 2
66
layout: learningpathall
77
---
88

9-
### What is Profile-Guided Optimization (PGO) and how does it work?
9+
## What is Profile-Guided Optimization?
1010

11-
Profile-Guided Optimization (PGO) is a compiler optimization technique that enhances program performance by utilizing real-world execution data. PGO typically involves a two-step process:
11+
Profile-Guided Optimization (PGO) is a compiler optimization technique that enhances your program's performance by using real-world execution data. PGO works in two steps:
1212

13-
- First, compile the program to produce an instrumented binary that collects profiling data during execution;
14-
- Second, recompile the program with an optimization profile, allowing the compiler to leverage the collected data to make informed optimization decisions. This approach identifies frequently executed paths — known as “hot” paths — and optimizes them more aggressively, while potentially reducing emphasis on less critical code paths.
13+
First, you compile your program to produce an instrumented binary that collects profiling data during execution. Second, you recompile the program with this optimization profile, allowing the compiler to make informed optimization decisions based on the collected data.
1514

16-
### When should I use Profile-Guided Optimization?
15+
This approach identifies frequently executed paths (known as "hot" paths) and optimizes them more aggressively, while reducing emphasis on less critical code paths.
1716

18-
PGO is particularly beneficial in the later stages of development when real-world workloads are available. It is especially useful for applications where performance is critical and runtime behavior is complex or data-dependent. For instance, consider optimizing “hot” functions that execute frequently. Doing so ensures that the most impactful parts of your code are optimized based on actual usage patterns.
17+
## How does PGO improve performance?
1918

20-
### What are the limitations of Profile-Guided Optimization and when should I avoid it?
19+
PGO enables several compiler optimizations that aren't possible with static analysis alone.
2120

22-
While PGO offers substantial performance benefits, it has limitations. The profiling data must accurately represent typical usage scenarios; otherwise, the optimizations may not deliver the desired performance improvements and could even degrade performance.
21+
The compiler arranges frequently executed code together in memory through code layout optimization, which improves instruction cache utilization and reduces branch mispredictions. Instead of using heuristics for inlining decisions, the compiler inlines functions based on actual call frequency and execution patterns from your profiling data.
2322

24-
Additionally, the process requires extra build steps, potentially increasing compile times for large codebases. Therefore, use PGO only on performance-critical sections that are heavily influenced by actual runtime behavior. PGO might not be ideal for early-stage development or applications with highly variable or unpredictable usage patterns.
23+
For C++ virtual functions, PGO can identify the most common call targets and optimize or devirtualize those paths. The compiler can also eliminate dead code by optimizing differently or removing code paths that never execute in your profiling runs.
2524

26-
For further information, see the [MSVC documentation](https://learn.microsoft.com/en-us/cpp/build/profile-guided-optimizations?view=msvc-170) on enabling and using PGO.
25+
For example, if your application has an error handling path that rarely executes, PGO ensures the compiler doesn't optimize for that path at the expense of your main execution flow. The result is typically 5-15% performance improvement, though gains vary by application.
26+
27+
## When should you use Profile-Guided Optimization?
28+
29+
You'll find PGO particularly beneficial in the later stages of development when real-world workloads are available. It's especially useful for applications where performance is critical and runtime behavior is complex or data-dependent.
30+
31+
For example, consider optimizing "hot" functions that execute frequently. By doing so, you ensure that the most impactful parts of your code are optimized based on actual usage patterns.
32+
33+
## Limitations of Profile-Guided Optimization
34+
35+
While PGO offers substantial performance benefits, it has some limitations to keep in mind.
36+
37+
Your profiling data must accurately represent typical usage scenarios. If it doesn't, the optimizations might not deliver the desired performance improvements and could even degrade performance.
38+
39+
Additionally, the process requires extra build steps, which can increase compile times for large codebases. Use PGO on performance-critical sections that are heavily influenced by actual runtime behavior.
40+
41+
PGO might not be ideal for early-stage development or applications with highly variable or unpredictable usage patterns.
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
---
2-
title: Google Benchmark
2+
title: Understand Google Benchmark basics
33
weight: 3
44

55
### FIXED, DO NOT MODIFY
66
layout: learningpathall
77
---
88

9-
## Google Benchmark
9+
Before you start working with Profile-Guided Optimization, you need to understand how to measure performance. This section introduces Google Benchmark, the tool you'll use to measure the impact of your optimizations. Don't worry about installing anything yet. You'll set up your environment and run your first benchmark in the next section.
1010

11-
Google Benchmark is a C++ library specifically designed for microbenchmarkingmeasuring the performance of small code snippets with high accuracy. Microbenchmarking is essential for identifying bottlenecks and optimizing critical sections, especially in performance-sensitive applications.
11+
Google Benchmark is a C++ library specifically designed for microbenchmarking, which means measuring the performance of small code snippets with high accuracy. Microbenchmarking helps you identify bottlenecks and optimize critical sections, especially in performance-sensitive applications.
1212

13-
Google Benchmark simplifies this process by providing a framework that manages iterations, times execution, and performs statistical analysis. This allows you to focus on the code being measured, rather than writing boilerplate or trying to prevent unwanted compiler optimizations manually.
13+
Google Benchmark simplifies this process by providing a framework that manages iterations, times execution, and performs statistical analysis. This lets you focus on the code being measured, rather than writing test code or trying to prevent unwanted compiler optimizations manually.
1414

15-
To use Google Benchmark, define a function that accepts a `benchmark::State&` parameter and iterate over it to perform the benchmarking. Register the function using the `BENCHMARK` macro and include `BENCHMARK_MAIN()` to generate the benchmark's entry point.
15+
To use Google Benchmark, you define a function that accepts a `benchmark::State&` parameter and iterate over it to perform the benchmarking. You register the function using the `BENCHMARK` macro and include `BENCHMARK_MAIN()` to generate the benchmark's entry point.
1616

17-
Here's a basic example:
17+
This example shows a basic benchmark that measures the time it takes to create an empty string:
1818

1919
```cpp
2020
#include <benchmark/benchmark.h>
@@ -28,18 +28,18 @@ BENCHMARK(BM_StringCreation);
2828
BENCHMARK_MAIN();
2929
```
3030
31-
### Filtering and Preventing Compiler Optimizations
31+
## Filter benchmarks and prevent compiler optimizations
3232
3333
Google Benchmark provides tools to ensure accurate measurements by preventing unintended compiler optimizations and allowing flexible benchmark selection.
3434
35-
1. **Preventing Optimizations**: Use `benchmark::DoNotOptimize(value);` to force the compiler to read and store a variable or expression, ensuring it is not optimized away.
36-
37-
2. **Filtering Benchmarks**: To run a specific subset of benchmarks, use the `--benchmark_filter` command-line option with a regular expression. For example:
35+
To prevent the compiler from optimizing away your code, use `benchmark::DoNotOptimize(value);` to force the compiler to read and store a variable or expression. This ensures your benchmark actually measures what you intend to measure.
3836
39-
```bash
40-
.\benchmark_binary --benchmark_filter=BM_String.*
41-
```
42-
43-
This eliminates the need to repeatedly comment out lines of source code.
37+
When you have multiple benchmarks, you can run a specific subset using the `--benchmark_filter` command-line option with a regular expression. This example runs all benchmarks that start with "BM_String":
4438
45-
For more detailed information and advanced usage, refer to the [official documentation](https://github.com/google/benchmark).
39+
```console
40+
.\benchmark_binary --benchmark_filter=BM_String.*
41+
```
42+
43+
This approach eliminates the need to repeatedly comment out lines of source code when you want to focus on specific benchmarks.
44+
45+
Now that you understand how Google Benchmark works, you're ready to install it and run your first benchmark. In the next section, you'll set up your environment and use Google Benchmark with Profile-Guided Optimization to measure and improve performance.

content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/how-to-3.md

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
---
2-
title: Example operation
2+
title: Create a baseline benchmark
33
weight: 4
44

55
### FIXED, DO NOT MODIFY
66
layout: learningpathall
77
---
88

9-
## Optimizing costly division operations with Google Benchmark and PGO
9+
In this section, you'll create a baseline benchmark to measure the performance of a division operation. This baseline will help you see the improvement when you apply Profile-Guided Optimization in the next section. This example demonstrates how even seemingly straightforward operations can benefit from optimization techniques.
1010

11-
In this section, you'll learn how to use Google Benchmark and Profile-Guided Optimization to improve the performance of a simple division operation. This example demonstrates how even seemingly straightforward operations can benefit from optimization techniques.
11+
Integer division is ideal for benchmarking because it's significantly more expensive than operations like addition, subtraction, or multiplication. On most CPU architectures, including Arm, division instructions have higher latency and lower throughput compared to other arithmetic operations. By applying Profile-Guided Optimization to code containing division operations, you can achieve significant performance improvements.
1212

13-
Integer division is ideal for benchmarking because it's significantly more expensive than operations like addition, subtraction, or multiplication. On most CPU architectures, including Arm, division instructions have higher latency and lower throughput compared to other arithmetic operations. By applying Profile-Guided Optimization to code containing division operations, we can potentially achieve significant performance improvements.
13+
For this example, you'll use an Arm computer running Windows.
1414

15-
For this example, you can use an Arm computer running Windows.
15+
## Install the required tools
1616

17-
## What tools are needed to run a Google Benchmark example on Windows?
17+
### Install vcpkg and Google Benchmark
1818

19-
Download the [Arm GNU Toolchain](https://developer.arm.com/Tools%20and%20Software/GNU%20Toolchain) to install the prerequisite packages.
20-
21-
Next, install the static version of Google Benchmark for Arm64 via vcpkg. Run the following commands in Powershell as Administrator:
19+
Run the following commands in PowerShell:
2220

2321
```console
24-
cd C:\git
25-
git clone https://github.com/microsoft/vcpkg.git
26-
cd vcpkg
27-
.\bootstrap-vcpkg.bat
28-
.\vcpkg install benchmark:arm64-windows-static
22+
iex (iwr -useb https://aka.ms/vcpkg-init.ps1)
23+
cd $HOME
24+
mkdir pgo-benchmark
25+
cd pgo-benchmark
26+
& "$HOME\.vcpkg\vcpkg.exe" new --application
27+
& "$HOME\.vcpkg\vcpkg.exe" add port benchmark
28+
& "$HOME\.vcpkg\vcpkg.exe" install
2929
```
3030

31-
## Division example
31+
## Create the division benchmark
3232

3333
Use an editor to copy and paste the C++ source code below into a file named `div_bench.cpp`.
3434

35-
This trivial example takes in a vector of 4096 32-bit integers and divides each element by a number. Importantly, the use of `benchmark/benchmark.h` introduces indirection since the divisor value is unknown at compile time, although it is visible in the source code as 1500.
35+
This example takes a vector of 4096 32-bit integers and divides each element by a number. The key detail here is that the divisor value is passed through `s.range(0)`, making it unknown at compile time. This prevents the compiler from applying optimizations like strength reduction, which means PGO will have an opportunity to make a real difference.
3636

3737
```cpp
3838
#include <benchmark/benchmark.h>
@@ -54,21 +54,34 @@ BENCHMARK(baseDiv)->Arg(1500)->Unit(benchmark::kMicrosecond); // value of 1500 i
5454
BENCHMARK_MAIN();
5555
```
5656
57-
To compile and run the microbenchmark on this function, you need to link with the correct libraries:
57+
## Compile and run the baseline benchmark
58+
59+
Open an **ARM64 Native Tools Command Prompt** from the Windows Start menu and start PowerShell:
60+
61+
```console
62+
powershell
63+
```
64+
65+
Before compiling, set an environment variable to refer to the vcpkg installation directory:
5866

59-
Compile with the command:
67+
```console
68+
$VCPKG="$HOME\pgo-benchmark\vcpkg_installed\arm64-windows"
69+
```
70+
71+
Now compile the benchmark. This command uses the MSVC compiler and links with the Google Benchmark libraries:
6072

6173
```console
62-
cl /D BENCHMARK_STATIC_DEFINE div_bench.cpp /link /LIBPATH:"$VCPKG\lib" benchmark.lib benchmark_main.lib shlwapi.lib
74+
cl /I"$VCPKG\include" /D BENCHMARK_STATIC_DEFINE div_bench.cpp /link /LIBPATH:"$VCPKG\lib" benchmark.lib benchmark_main.lib shlwapi.lib
6375
```
6476

65-
Run the program:
77+
Run the program to establish your baseline performance:
6678

6779
```console
80+
$env:PATH += ";$HOME\pgo-benchmark\vcpkg_installed\arm64-windows\bin"
6881
.\div_bench.exe
6982
```
7083

71-
### Example output
84+
The output is similar to:
7285

7386
```output
7487
Running ./div_bench.base
@@ -85,3 +98,5 @@ Benchmark Time CPU Iterations
8598
-------------------------------------------------------
8699
baseDiv/1500 7.90 us 7.90 us 88512
87100
```
101+
102+
The baseline shows an average execution time of 7.90 microseconds. This gives you a clear starting point to measure improvement. Now that you have this baseline measurement, you're ready to apply Profile-Guided Optimization. In the next section, you'll use PGO to optimize this code and see how much faster it can run.

0 commit comments

Comments
 (0)