Skip to content

Commit 19d420f

Browse files
Refactor CPU affinity learning path content for clarity and detail
1 parent 16fd356 commit 19d420f

4 files changed

Lines changed: 47 additions & 28 deletions

File tree

content/learning-paths/servers-and-cloud-computing/pinning-threads/_index.md

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
---
2-
title: Getting Started with CPU Affinity
3-
4-
draft: true
5-
cascade:
6-
draft: true
2+
title: Optimize application performance with CPU affinity
73

84
minutes_to_complete: 30
95

10-
who_is_this_for: Developers, performance engineers and system administrators, looking to fine-tune the performance of their workload on many-core Arm-based systems.
6+
who_is_this_for: This is an advanced topic for developers, performance engineers, and system administrators looking to fine-tune the performance of their workload on many-core Arm-based systems.
117

128
learning_objectives:
13-
- Create CPU Sets and implement directly into sourcecode
14-
- Understand the performance tradeoff when pinning threads with CPU affinity masks
9+
- Pin threads to specific CPU cores using taskset and source code modifications
10+
- Measure cache performance improvements from thread pinning using perf
11+
- Evaluate performance trade-offs between throughput and latency consistency
12+
- Implement CPU affinity strategies for co-located workloads
1513

1614
prerequisites:
17-
- Intermediate understanding of multi-threaded object-orientated programming in C++ and Python
18-
- Foundational understanding of build systems and computer architecture
15+
- An Arm Linux system with four or more CPU cores
16+
- Experience with multi-threaded programming in C++ and Python
17+
- Understanding of build systems and computer architecture concepts
18+
- Familiarity with Linux command-line tools
1919

2020
author: Kieran Hejmadi
2121

@@ -27,6 +27,9 @@ armips:
2727
tools_software_languages:
2828
- C++
2929
- Python
30+
- taskset
31+
- perf
32+
- Google Benchmark
3033
operatingsystems:
3134
- Linux
3235

@@ -35,6 +38,22 @@ further_reading:
3538
title: Taskset Manual
3639
link: https://man7.org/linux/man-pages/man1/taskset.1.html
3740
type: documentation
41+
- resource:
42+
title: pthread_setaffinity_np Manual
43+
link: https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html
44+
type: documentation
45+
- resource:
46+
title: NUMA Deep Dive
47+
link: https://frankdenneman.nl/2016/07/07/numa-deep-dive-part-1-uma-numa/
48+
type: documentation
49+
- resource:
50+
title: Linux Scheduler Documentation
51+
link: https://www.kernel.org/doc/html/latest/scheduler/index.html
52+
type: documentation
53+
- resource:
54+
title: Get started with Arm-based cloud instances
55+
link: /learning-paths/servers-and-cloud-computing/csp/
56+
type: website
3857

3958

4059

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
---
2-
title: Thread pinning fundamentals
2+
title: Understand thread pinning and CPU affinity
33
weight: 2
44

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

9-
## CPU affinity
9+
## Explore CPU affinity
1010

1111
CPU affinity is the practice of binding a process or thread to a specific CPU core or set of cores. This tells the operating system scheduler where that work is allowed to run. By default, the Linux scheduler dynamically migrates threads across cores to balance load and maximize overall throughput. Pinning overrides this behavior by constraining execution to a chosen set of cores.
1212

13-
## Pinning
13+
## Identify use cases for thread pinning
1414

1515
Pinning is most often used as a fine-tuning technique for workloads that aim to consume as many CPU cycles as possible while running alongside other demanding applications. Scientific computing pipelines and real-time analytics frequently fall into this category.
1616

1717
Applications that pin processes to specific cores are often sensitive to latency variation rather than just average throughput. They may also have intricate memory access patterns. Pinning can reduce execution noise and provide more consistent behavior or better memory access patterns under load.
1818

19-
## Memory locality
19+
## Improve memory locality on NUMA systems
2020

2121
Memory locality provides another important motivation for pinning. On modern systems with Non-Uniform Memory Access (NUMA) architectures, different cores have varying memory access times and characteristics. The performance depends on where the data is fetched from.
2222

23-
For example, in a server with two CPU sockets that appears as a single processor, memory access times differ depending on which core you use. By pinning threads to cores that are close to the memory they use and allocating memory accordingly, you can reduce memory access latency and improve bandwidth.
23+
For example, in a server with two CPU sockets that appears as a single processor, memory access times differ depending on which core you use. By pinning threads to cores that are close to the memory they use and allocating memory accordingly, you reduce memory access latency and improve bandwidth.
2424

25-
## Setting affinity
25+
## Choose methods to set CPU affinity
2626

2727
You can set affinity directly in source code using system calls. Many parallel frameworks expose higher-level controls, such as OpenMP affinity settings, that manage thread placement automatically.
2828

2929
Alternatively, at runtime, system administrators can pin existing processes using utilities like `taskset` or launch applications with `numactl` to control both CPU and memory placement without modifying code.
3030

31-
## Conclusion
31+
## Evaluate trade-offs of thread pinning
3232

33-
Pinning is a tradeoff. It can improve determinism and locality, but it can also reduce flexibility and hurt performance if the chosen layout isn't optimal or if system load changes. Over-constraining the scheduler may lead to idle cores while pinned threads contend unnecessarily.
33+
Thread pinning is a trade-off. It can improve determinism and locality, but it can also reduce flexibility and hurt performance if the chosen layout isn't optimal or if system load changes. Over-constraining the scheduler may lead to idle cores while pinned threads contend unnecessarily.
3434

3535
As a general rule, rely on the operating system scheduler initially and introduce pinning only when you're looking to fine-tune performance after measuring baseline behavior.

content/learning-paths/servers-and-cloud-computing/pinning-threads/setup.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ This Learning Path works on any Arm Linux system with four or more CPU cores.
1212

1313
For example, you can use an AWS Graviton 3 `m7g.4xlarge` instance running Ubuntu 24.04 LTS, based on the Arm Neoverse V1 architecture.
1414

15-
If you're unfamiliar with creating a cloud instance, refer to [Get started with Arm-based cloud instances](/learning-paths/servers-and-cloud-computing/csp/).
15+
If you're unfamiliar with creating a cloud instance, see [Get started with Arm-based cloud instances](/learning-paths/servers-and-cloud-computing/csp/).
1616

1717
The `m7g.4xlarge` instance has a uniform processor architecture, so there's no difference in memory or CPU core performance across the cores.
1818

19-
On Linux, you can check this with the following command:
19+
On Linux, check this with the following command:
2020

2121
```bash
2222
lscpu | grep -i numa
@@ -57,9 +57,9 @@ Finally, install the Linux perf utility for measuring performance. See the [Linu
5757

5858
## Create a CPU-intensive example program
5959

60-
To demonstrate CPU affinity, you'll create a program that heavily utilizes all available CPU cores. This example repeatedly calculates the [Leibniz equation](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80) to compute the value of Pi. This is a computationally inefficient algorithm to calculate Pi, and you'll split the work across many threads.
60+
To demonstrate CPU affinity, you'll create a program that heavily utilizes all available CPU cores. This example repeatedly calculates the [Leibniz equation](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80) to compute the value of Pi. This is a computationally inefficient algorithm to calculate Pi, and the work is split across many threads.
6161

62-
Use an editor to create a file named `use_all_cores.cpp` with the code below:
62+
Create a file named `use_all_cores.cpp` with the code below. This program spawns multiple threads that calculate Pi using the Leibniz formula:
6363

6464
```cpp
6565
#include <vector>
@@ -75,7 +75,7 @@ double multiplethreaded_leibniz(int terms, bool use_all_cores){
7575

7676
int NUM_THREADS = 2; // use 2 cores by default
7777
if (use_all_cores){
78-
NUM_THREADS = std::thread::hardware_concurrency(); // e.g., 16 for a 16-core, single-threaded processor
78+
NUM_THREADS = std::thread::hardware_concurrency(); // for example, 16 for a 16-core, single-threaded processor
7979
}
8080
std::vector<double> partial_results(NUM_THREADS);
8181

@@ -143,7 +143,7 @@ g++ -O2 --std=c++11 use_all_cores.cpp -o prog
143143

144144
## Observe CPU utilization
145145

146-
Now that you've compiled the program, you can observe how it utilizes CPU cores. In a separate terminal, use the `top` utility to view the utilization of each core:
146+
Observe how the compiled program utilizes CPU cores. In a separate terminal, use the `top` utility to view the utilization of each core:
147147

148148
```bash
149149
top -d 0.1
@@ -157,9 +157,9 @@ Then run the program in the other terminal:
157157
./prog
158158
```
159159

160-
![Screenshot of the top command showing CPU utilization with all 16 cores periodically reaching 100% usage, displayed in a dark terminal window with percentage bars for each CPU core](cpu_util.jpg "CPU utilization showing all cores being used")
160+
![Terminal output showing the top command displaying system resource usage. Sixteen CPU cores labeled CPU0 through CPU15 are shown with horizontal percentage bars. Most cores show near 100% utilization with full green bars. The display includes columns for %Cpu(s), memory usage, and process information in a dark theme terminal window alt-txt#center](cpu_util.jpg "CPU utilization showing all cores being used")
161161

162-
You should observe all cores on your system being periodically utilized up to 100% and then dropping to idle until the program exits.
162+
All cores on your system are periodically utilized up to 100% and then drop to idle until the program exits.
163163

164164
## What you've accomplished and what's next
165165

content/learning-paths/servers-and-cloud-computing/pinning-threads/thread_affinity.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ This program has two atomic variables that are aligned on different cache lines
8080
8181
## Create a program with explicit thread pinning
8282
83-
Now, copy the code below into a new file named `thread_affinity.cpp`:
83+
Create a file named `thread_affinity.cpp` with the code below. This program uses `pthread_setaffinity_np` to pin threads to specific CPU cores:
8484
8585
```cpp
8686
#include <benchmark/benchmark.h>
@@ -178,7 +178,7 @@ perf stat -e L1-dcache-loads,L1-dcache-load-misses ./thread-affinity
178178

179179
## Analyze the performance results
180180

181-
Inspecting the output below, you can see that the `L1-dcache-load-misses` metric (which occurs when the CPU core doesn't have an up-to-date version of the data in the L1 data cache and must perform an expensive operation to fetch data from a different location) reduces from approximately 7.84% to approximately 0.6% as a result of thread pinning.
181+
The output shows the `L1-dcache-load-misses` metric reduces from approximately 7.84% to approximately 0.6% as a result of thread pinning. This metric measures how often the CPU core doesn't have an up-to-date version of data in the L1 data cache and must perform an expensive operation to fetch data from a different location.
182182

183183
This results in a significant reduction in function execution time, dropping from 10.7 ms to 3.53 ms:
184184

0 commit comments

Comments
 (0)