You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/learning-paths/servers-and-cloud-computing/pinning-threads/_index.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@ cascade:
7
7
8
8
minutes_to_complete: 30
9
9
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.
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.
11
11
12
12
learning_objectives:
13
13
- Create CPU Sets and implement directly into sourcecode
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.
11
12
12
-
CPU affinity is the practice of binding a process or thread to a specific CPU core or set of cores, telling 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.
13
+
## Pinning
13
14
14
-
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 realtime analytics frequently fall into this category. Typical applications that pin processes to specific cores are often sensitive to latency variation rather than just average throughput or have intricate memory access patterns. Pinning can reduce this noise and provide more consistent execution behavior or better memory access patterns under load.
15
+
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.
15
16
16
-
Another important motivation is memory locality. On modern systems with Non Uniform Memory Access architectures (NUMA), different cores have memory access times and characteristics depending on where the data is fetched from. For example, in a server with 2 CPU sockets, that from a programmers view appears as a single processor, would have different memory access times depending on the core. By pinning threads to cores that are close to the memory they use and allocating memory accordingly, an application can reduce memory access latency and improve bandwidth.
17
+
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.
17
18
18
-
Developers 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. 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.
19
+
## Memory locality
19
20
20
-
Pinning is a tradeoff. It can improve determinism and locality but it can also reduce flexibility and hurt performance if the chosen layout is suboptimal or if system load changes. Over constraining the scheduler may lead to idle cores while pinned threads contend unnecessarily. As a general rule it is best to rely on the operating system scheduler as a first pass and introduce pinning only if you are looking to fine-tune performance.
21
+
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.
22
+
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.
24
+
25
+
## Setting affinity
26
+
27
+
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.
28
+
29
+
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.
30
+
31
+
## Conclusion
32
+
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.
34
+
35
+
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.
Copy file name to clipboardExpand all lines: content/learning-paths/servers-and-cloud-computing/pinning-threads/setup.md
+46-31Lines changed: 46 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,5 @@
1
1
---
2
-
title: Setup
2
+
title: Create a CPU-intensive program
3
3
weight: 3
4
4
5
5
### FIXED, DO NOT MODIFY
@@ -8,60 +8,58 @@ layout: learningpathall
8
8
9
9
## Setup
10
10
11
-
In this example we will be using an AWS Graviton 3 `m7g.4xlarge` instance running Ubuntu 22.04 LTS, based on the Arm Neoverse V1 architecture. If you are unfamiliar with creating a cloud instance, please refer to our [getting started learning path](https://learn.arm.com/learning-paths/servers-and-cloud-computing/csp/).
11
+
This Learning Path works on any Arm Linux system with four or more CPU cores.
12
12
13
-
This learning path is expected to work on any linux-based Arm instance with 4 or more CPU cores. The `m7g.4xlarge` instance has a uniform processor architecture so there is neglible different in memory or CPU core performance across the cores. On Linux, this can easily be checked with the following command.
13
+
For example, you can use an AWS Graviton 3 `m7g.4xlarge` instance running Ubuntu 24.04 LTS, based on the Arm Neoverse V1 architecture.
14
+
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/).
16
+
17
+
The `m7g.4xlarge` instance has a uniform processor architecture, so there's no difference in memory or CPU core performance across the cores.
18
+
19
+
On Linux, you can check this with the following command:
14
20
15
21
```bash
16
22
lscpu | grep -i numa
17
23
```
18
24
19
-
For our`m7g.4xlarge` all 16 cores are in the same NUMA (non-uniform memory architecture) node.
25
+
For the`m7g.4xlarge`, all 16 cores are in the same NUMA node:
20
26
21
-
```out
27
+
```output
22
28
NUMA node(s): 1
23
29
NUMA node0 CPU(s): 0-15
24
30
```
25
31
26
-
First we will demonstrate how we can pin threads easily using the `taskset` utility available in Linux. This is used to set or retrieve the CPU affinity of a running process or set the affinity of a process about to be launched. This does not require any modifications to the source code.
32
+
You'll first learn how to pin threads using the `taskset` utility available in Linux.
27
33
34
+
This utility sets or retrieves the CPU affinity of a running process or sets the affinity of a process about to be launched. This approach doesn't require any modifications to the source code.
28
35
29
-
## Install Prerequisites
36
+
## Install prerequisites
30
37
31
-
Run the following commands:
38
+
Run the following commands to install the required packages:
If you have issues building and installing, please refer to the [official installation guide](https://github.com/google/benchmark).
53
53
54
-
Finally, you will need to install the Linux perf utility for measuring performance. We recommend using our [install guide](https://learn.arm.com/install-guides/perf/). As you may need to build from source.
54
+
If you have issues building and installing, visit the [Benchmark repository](https://github.com/google/benchmark).
55
55
56
-
## Example 1
56
+
Finally, install the Linux perf utility for measuring performance. See the [Linux Perf install guide](/install-guides/perf/) as you may need to build from source.
57
57
58
-
To demonstrate a use case of CPU affinity, we will create a program that heavily utilizes all the available CPU cores. Create a file named `use_all_cores.cpp` and paste in the source code below. In this example, we are repeatedly calculating 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 the value of Pi and we are splitting the work across many threads.
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.
64
61
62
+
Use an editor to create a file named `use_all_cores.cpp` with the code below:
In a separate terminal we can use the `top` utility to quickly view the utilization of each core. For example, run the following command and press the number `1`. Then we can run the program by entering `./prog`.
144
+
## Observe CPU utilization
145
+
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:
147
+
148
+
```bash
149
+
top -d 0.1
150
+
```
151
+
152
+
Press the number `1` to view per-core utilization.
153
+
154
+
Then run the program in the other terminal:
149
155
150
156
```bash
151
-
top -d 0.1 # then press 1 to view per core utilization
157
+
./prog
152
158
```
153
159
154
-

160
+

161
+
162
+
You should observe all cores on your system being periodically utilized up to 100% and then dropping to idle until the program exits.
163
+
164
+
## What you've accomplished and what's next
165
+
166
+
In this section, you've:
167
+
- Set up an AWS Graviton 3 instance and installed the required tools
168
+
- Created a multi-threaded program that heavily utilizes all available CPU cores
169
+
- Observed how the program distributes work across cores using the `top` utility
155
170
156
-
As the screenshot above shows, you should observe all cores on your system being periodically utilized up to 100% and then down to idle until the program exits. In the next section we will look at how to bind this program to specific CPU cores when running alongside a single-threaded Python script.
171
+
In the next section, you'll learn how to bind this program to specific CPU cores when running alongside a single-threaded Python script.
Copy file name to clipboardExpand all lines: content/learning-paths/servers-and-cloud-computing/pinning-threads/thread_affinity.md
+37-14Lines changed: 37 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,16 +1,22 @@
1
1
---
2
-
title: CPU Affinity
2
+
title: Set CPU affinity in source code
3
3
weight: 5
4
4
5
5
### FIXED, DO NOT MODIFY
6
6
layout: learningpathall
7
7
---
8
8
9
-
## Pinning Threads at Source-Code Level
9
+
## Pin threads at the source code level
10
10
11
-
Another way to set CPU affinity is at the source code level, this allows developers to be more expressive as to which thread goes where at specific points during the runtime. For example, in a hot path that repeatedly updates shared state with a read-modify-write style, a pinned thread could avoids excessive cache invalidations due to other threads modifying data.
11
+
Another way to set CPU affinity is at the source code level. This allows you to be more expressive about which thread goes where at specific points during runtime.
12
12
13
-
To demonstrate this we have an example program below. Copy and paste the code below into a new file named `default_os_scheduling.cpp`.
13
+
For example, in a hot path that repeatedly updates shared state with a read-modify-write pattern, a pinned thread can avoid excessive cache invalidations caused by other threads modifying data.
14
+
15
+
## Create a baseline program without thread pinning
16
+
17
+
To demonstrate this, you'll create two example programs. The first uses the default OS scheduling without thread pinning.
18
+
19
+
Copy and paste the code below into a new file named `default_os_scheduling.cpp`:
`default_os_scheduling.cpp` has 2 atomic variables that are aligned on different cache lines to avoid thrashing. We spawn 4 threads, with 2 threads performing a read-modify-wite operation on the first atomic variable, and the final 2 threads performing the same operation on the second atomic variable.
79
+
This program has two atomic variables that are aligned on different cache lines to avoid thrashing. You spawn four threads: two threads perform a read-modify-write operation on the first atomic variable, and two threads perform the same operation on the second atomic variable.
74
80
75
-
Now, copy the code block below into a file named `thread_affinity.cpp`.
81
+
## Create a program with explicit thread pinning
82
+
83
+
Now, copy the code below into a new file named `thread_affinity.cpp`:
`Thread_affinity.cpp` uses the `pthread_set_affinity_np` function from the `pthread.h` header file to pin the 2 threads operating on atomic variable, `a`, to a specific CPU set and the other threads operating on atomic variable, `b`, to a different CPU.
161
+
This program uses the `pthread_setaffinity_np` function from the `pthread.h` header file to pin threads. The two threads operating on atomic variable `a` are pinned to a specific CPU set, and the other threads operating on atomic variable `b` are pinned to a different CPU.
162
+
163
+
## Compile and benchmark the programs
154
164
155
-
Compile both programs with the following command.
165
+
Compile both programs with the following commands:
We will use the `perf` tool to print statistic for the program.
172
+
Use Perf to print statistics for both programs:
163
173
164
174
```bash
165
175
perf stat -e L1-dcache-loads,L1-dcache-load-misses ./default-os-scheduling
166
176
perf stat -e L1-dcache-loads,L1-dcache-load-misses ./thread-affinity
167
177
```
168
178
169
-
Inspecting the output below we see that the `L1-dcache-load-misses` which occur when the the CPU core does not have a 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 ~7.84% to ~0.6% as a result of the thread pinning. This results in a huge reduction in function execution time, dropping from 10.7ms to 3.53ms.
179
+
## Analyze the performance results
170
180
171
-
```outputRunning ./default-os-scheduling
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.
182
+
183
+
This results in a significant reduction in function execution time, dropping from 10.7 ms to 3.53 ms:
184
+
185
+
```output
186
+
Running ./default-os-scheduling
172
187
Run on (16 X 2100 MHz CPU s)
173
188
CPU Caches:
174
189
L1 Data 64 KiB (x16)
@@ -217,8 +232,16 @@ thread_affinity/real_time 3.53 ms 0.343 ms 198
217
232
0.169065000 seconds sys
218
233
```
219
234
220
-
### Conclusion
235
+
The results demonstrate that thread pinning can significantly improve performance when threads operate on separate data structures. By keeping threads on specific cores, you reduce cache coherency traffic and improve data locality.
236
+
237
+
## What you've accomplished and what's next
238
+
239
+
In this section, you've:
240
+
- Created two programs to compare default OS scheduling against explicit thread pinning
241
+
- Used the `pthread_setaffinity_np` API to control CPU affinity at the source code level
242
+
- Measured cache performance using Perf to quantify the impact of thread pinning
243
+
- Observed a performance improvement and a reduction in cache misses through strategic thread placement
221
244
222
-
In this tutorial, we introduced thread pinning (CPU affinity) through a pair of worked examples. By comparing default OS thread scheduling against explicitly pinned threads, we showed how controlling where threads run can reduce cache disruption in contention-heavy paths and improve runtime stability and performance.
245
+
You've seen how controlling where threads run can reduce cache disruption in contention-heavy paths and improve runtime stability and performance. You've also learned about the trade-offs: pinning can boost locality and predictability, but it can hurt performance of other running processes, especially if the workload characteristics change or if you over-constrain the scheduler.
223
246
224
-
We also highlighted the tradeoffs, pinning can boost locality and predictability, but it can hurt performance of other running processes, espec. Finally, we showed how to implement affinity quickly using common system utilities for inspection and measurement, and how to be more expressive directly in code using APIs like `pthread_setaffinity_np` from `pthread.h`.
247
+
Thread pinning is most effective when you have well-understood workload patterns and clear separation between data structures accessed by different threads. Use it as a fine-tuning technique after establishing baseline performance with default OS scheduling.
0 commit comments