使用 C 有效迁移 linux 进程
Effectively migrate a linux process with C
我需要估算在同一台计算机的另一个核心上迁移 linux 进程需要多少费用。要迁移我正在使用 sched_setaffinity 系统调用的进程,但我注意到迁移并不总是立即发生,这是我的要求。
更深入地说,我正在创建一个 C 程序,它每次进行两次大量简单计算,第一次没有迁移,第二次有迁移。计算两个时间戳之间的差异应该可以让我粗略估计迁移开销。但是,我需要弄清楚如何迁移当前进程并等待迁移发生
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 199309L
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
//Migrates the process
int migrate(pid_t pid) {
const int totCPU = 8;
const int nextCPU = (sched_getcpu() +1) % totCPU;
cpu_set_t target;
CPU_SET(nextCPU, &target);
if(sched_setaffinity(pid, sizeof(target), &target) < 0)
perror("Setaffinity");
return nextCPU;
}
int main(void) {
long i =0;
const long iterations = 4;
uint64_t total_sequential_delays = 0;
uint64_t total_migration_delays = 0;
uint64_t delta_us;
for(int i=0; i < iterations; i++) {
struct timespec start, end;
//Migration benchmark only happens in odd iterations
bool do_migration = i % 2 == 1;
//Start timestamp
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
//Target CPU to migrate
int target;
if(do_migration) {
target = migrate(0);
//if current CPU is not the target CPU
if(target != sched_getcpu()) {
do {
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
}
while(target != sched_getcpu());
}
}
//Simple computation
double k = 5;
for(int j = 1; j <= 9999; j++) {
k *= j / (k-3);
}
//End timestamp
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
//Elapsed time
delta_us = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
if(do_migration) total_migration_delays += delta_us;
else total_sequential_delays += delta_us;
}
//Compute the averages
double avg_migration = total_migration_delays / iterations;
double avg_sequential = total_sequential_delays / iterations;
//Print them
printf("\navg_migration=%f, avg_sequential=%f",avg_migration,avg_sequential);
return EXIT_SUCCESS;
}
这里的问题是 do-while 循环(第 46-49 行)有时会永远运行。
您没有清除目标集:
cpu_set_t target;
CPU_ZERO(&target); /* you need to add this line */
CPU_SET(nextCPU, &target);
所以你不知道你设置的亲和力是什么。
另一个问题是迁移成本不是预先支付的,因此简单地测量从 set_affinity() 到您安顿下来的时间并不是一个真正正确的测量方法。您最好 运行 将工作负载包含在单个 cpu 中,然后是两个、三个。此外,请考虑您的工作负载是否应该涉及遍历较大的数据结构,可能还有更新,因为您的缓存投资损失也是迁移成本。
I need to estimate how much it costs to migrate a linux process on another core of the same computer.
OK,费用可以估算为:
设置新的 CPU affinity 并执行 "yield" 或“sleep(0)
” 以强制任务 switch/reschedule 所花费的时间(包括任务切换开销等)。
未来每次 "was cached on the old CPU but not cached in the new CPU yet" 内存访问的缓存未命中成本
每次未来 "virtual to physical translation was cached on the old CPU but not cached in the new CPU yet" 内存访问的 TLB 未命中成本
NUMA 处罚
负载平衡问题(例如,从 "lightly loaded" CPU 或核心迁移到 "heavily loaded by other processes" CPU 或核心可能会导致严重的性能问题,包括内核决定将其他进程迁移到不同的 CPU 以修复负载平衡的成本,其中其他进程支付的 costs/overheads 可能应该包含在迁移进程造成的总成本中)。 =15=]
注意:
a) 有多个级别的缓存(跟踪缓存、指令缓存、L1 数据缓存、L2 数据缓存,..)并且一些缓存在一些 CPU 之间共享(例如 L1 可能是共享的在同一核心内的逻辑 CPU 之间,L2 可能由 2 个核心共享,L3 可能由 8 个核心共享)。
b) TLB 未命中成本取决于很多因素(例如,如果内核在没有 PCID 功能的情况下使用 Meltdown 缓解措施并且无论如何都会在每次系统调用时清除 TLB 信息)。
c) NUMA 惩罚是延迟成本 - 每次访问在前一个 CPU 上分配的 RAM(例如缓存未命中)(对于前一个 NUMA 节点)将比访问 RAM 具有更高的延迟分配在 new/current CPU(正确的 NUMA 节点)上。
d) 所有缓存未命中成本、TLB 未命中成本和 NUMA 惩罚都取决于内存访问模式。没有内存访问的基准测试会产生误导。
e) 高速缓存未命中成本、TLB 未命中成本和 NUMA 惩罚高度依赖于所涉及的硬件 - 例如一台 "slow CPUs with fast RAM and no NUMA" 计算机上的基准测试与另一台 "fast CPUs with slow RAM and many NUMA domains" 计算机完全无关。同样,它高度依赖于 CPUs(例如,从 CPU #0 迁移到 CPU #1 可能花费很少,而从 CPU #0 迁移到CPU #15 可能很贵)。
To migrate the process I'm using the sched_setaffinity system call, but i've noticed that migration does not always happens instantaneously, which is my requirement.
在“sched_setaffinity();
”之后加上“sleep(0);
”。
我需要估算在同一台计算机的另一个核心上迁移 linux 进程需要多少费用。要迁移我正在使用 sched_setaffinity 系统调用的进程,但我注意到迁移并不总是立即发生,这是我的要求。
更深入地说,我正在创建一个 C 程序,它每次进行两次大量简单计算,第一次没有迁移,第二次有迁移。计算两个时间戳之间的差异应该可以让我粗略估计迁移开销。但是,我需要弄清楚如何迁移当前进程并等待迁移发生
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 199309L
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
//Migrates the process
int migrate(pid_t pid) {
const int totCPU = 8;
const int nextCPU = (sched_getcpu() +1) % totCPU;
cpu_set_t target;
CPU_SET(nextCPU, &target);
if(sched_setaffinity(pid, sizeof(target), &target) < 0)
perror("Setaffinity");
return nextCPU;
}
int main(void) {
long i =0;
const long iterations = 4;
uint64_t total_sequential_delays = 0;
uint64_t total_migration_delays = 0;
uint64_t delta_us;
for(int i=0; i < iterations; i++) {
struct timespec start, end;
//Migration benchmark only happens in odd iterations
bool do_migration = i % 2 == 1;
//Start timestamp
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
//Target CPU to migrate
int target;
if(do_migration) {
target = migrate(0);
//if current CPU is not the target CPU
if(target != sched_getcpu()) {
do {
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
}
while(target != sched_getcpu());
}
}
//Simple computation
double k = 5;
for(int j = 1; j <= 9999; j++) {
k *= j / (k-3);
}
//End timestamp
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
//Elapsed time
delta_us = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
if(do_migration) total_migration_delays += delta_us;
else total_sequential_delays += delta_us;
}
//Compute the averages
double avg_migration = total_migration_delays / iterations;
double avg_sequential = total_sequential_delays / iterations;
//Print them
printf("\navg_migration=%f, avg_sequential=%f",avg_migration,avg_sequential);
return EXIT_SUCCESS;
}
这里的问题是 do-while 循环(第 46-49 行)有时会永远运行。
您没有清除目标集:
cpu_set_t target;
CPU_ZERO(&target); /* you need to add this line */
CPU_SET(nextCPU, &target);
所以你不知道你设置的亲和力是什么。 另一个问题是迁移成本不是预先支付的,因此简单地测量从 set_affinity() 到您安顿下来的时间并不是一个真正正确的测量方法。您最好 运行 将工作负载包含在单个 cpu 中,然后是两个、三个。此外,请考虑您的工作负载是否应该涉及遍历较大的数据结构,可能还有更新,因为您的缓存投资损失也是迁移成本。
I need to estimate how much it costs to migrate a linux process on another core of the same computer.
OK,费用可以估算为:
设置新的 CPU affinity 并执行 "yield" 或“
sleep(0)
” 以强制任务 switch/reschedule 所花费的时间(包括任务切换开销等)。未来每次 "was cached on the old CPU but not cached in the new CPU yet" 内存访问的缓存未命中成本
每次未来 "virtual to physical translation was cached on the old CPU but not cached in the new CPU yet" 内存访问的 TLB 未命中成本
NUMA 处罚
负载平衡问题(例如,从 "lightly loaded" CPU 或核心迁移到 "heavily loaded by other processes" CPU 或核心可能会导致严重的性能问题,包括内核决定将其他进程迁移到不同的 CPU 以修复负载平衡的成本,其中其他进程支付的 costs/overheads 可能应该包含在迁移进程造成的总成本中)。 =15=]
注意:
a) 有多个级别的缓存(跟踪缓存、指令缓存、L1 数据缓存、L2 数据缓存,..)并且一些缓存在一些 CPU 之间共享(例如 L1 可能是共享的在同一核心内的逻辑 CPU 之间,L2 可能由 2 个核心共享,L3 可能由 8 个核心共享)。
b) TLB 未命中成本取决于很多因素(例如,如果内核在没有 PCID 功能的情况下使用 Meltdown 缓解措施并且无论如何都会在每次系统调用时清除 TLB 信息)。
c) NUMA 惩罚是延迟成本 - 每次访问在前一个 CPU 上分配的 RAM(例如缓存未命中)(对于前一个 NUMA 节点)将比访问 RAM 具有更高的延迟分配在 new/current CPU(正确的 NUMA 节点)上。
d) 所有缓存未命中成本、TLB 未命中成本和 NUMA 惩罚都取决于内存访问模式。没有内存访问的基准测试会产生误导。
e) 高速缓存未命中成本、TLB 未命中成本和 NUMA 惩罚高度依赖于所涉及的硬件 - 例如一台 "slow CPUs with fast RAM and no NUMA" 计算机上的基准测试与另一台 "fast CPUs with slow RAM and many NUMA domains" 计算机完全无关。同样,它高度依赖于 CPUs(例如,从 CPU #0 迁移到 CPU #1 可能花费很少,而从 CPU #0 迁移到CPU #15 可能很贵)。
To migrate the process I'm using the sched_setaffinity system call, but i've noticed that migration does not always happens instantaneously, which is my requirement.
在“sched_setaffinity();
”之后加上“sleep(0);
”。