跨上下文切换使用 rdtsc + rdtscp
Using rdtsc + rdtscp across context switch
我正在尝试编写一个程序来测量上下文切换。我已经完成了关于 rdtsc + rdtscp 指令的Intel's manual。
现在,我想跨上下文切换使用这些时间戳指令。我的大致骨架如下:
// init two pipes P1, P2
fork();
set_affinity(); // same core
// parent's code:
cpuid + rdtsc // start timer
write(timer to P1);
read(timer from P2); // blocks parent if timer value not written
rdtscp + cpuid // stop timer, get difference
// child's code:
read(timer from P1); // blocks child if timer value not written
rdtscp + cpuid // stop timer, get difference
cpuid + rdtsc // start timer
write(timer to P2);
我发现这段代码存在一些问题。假设计时器操作正确,
如果 OS 选择上下文切换到某个完全不同的进程(不是子进程或父进程),它将不起作用。
此代码还将包括 read() 和 write() 系统调用所用的时间。
忽略这些问题,是否有效使用 rdtsc + rdtscp 指令?
I know writing a kernel module and disabling preemption/interrupts is a better way
我以前做过,它似乎是测量上下文切换时间的有效方法。每当对这种细粒度的事情进行计时时,调度的不可预测性总是会发挥作用;通常,您通过测量数千次并寻找诸如最小时间间隔、媒体时间间隔或平均时间间隔之类的数字来处理这个问题。您可以通过 运行 使两个进程具有实时 SCHED_FIFO
优先级来减少调度问题。如果您想知道实际的 切换 时间(在单个 cpu 核心上),您需要将两个进程绑定到具有亲和力设置的单个 cpu。如果您只想知道一个进程能够响应另一个进程的输出的延迟,让它们 运行 在不同的 cpu 上就可以了。
另一个要记住的问题是,自愿和非自愿的上下文切换,以及从 user-space 和 kernel-space 开始的切换有不同的成本。你的很可能是自愿的。非自愿测量更难,需要从繁忙的循环或类似的情况中查看共享内存。
我使用了类似的计时代码,除了我有 1000000 次父循环,并在父子循环中为整个循环计时。附上代码。然后我修改它以计时各个上下文切换,就像在您的伪代码中一样,将 1000000 个单独时间相加并与我的原始代码非常一致。因此,考虑到已经提到的注意事项,任何一种方式似乎都有效。
我觉得有趣的是,当使用 sched_setaffinity()
将父项和子项设置为 运行 时,上下文切换时间增加了一倍多 cpus。为什么这会以这种方式影响时间?在同一 cpu 上进程 运行ning 之间的管道是否更快?
rdtscp.h:
static inline unsigned long rdtscp_start(void) {
unsigned long var;
unsigned int hi, lo;
__asm volatile ("cpuid\n\t"
"rdtsc\n\t" : "=a" (lo), "=d" (hi)
:: "%rbx", "%rcx");
var = ((unsigned long)hi << 32) | lo;
return (var);
}
static inline unsigned long rdtscp_end(void) {
unsigned long var;
unsigned int hi, lo;
__asm volatile ("rdtscp\n\t"
"mov %%edx, %1\n\t"
"mov %%eax, %0\n\t"
"cpuid\n\t" : "=r" (lo), "=r" (hi)
:: "%rax", "%rbx", "%rcx", "%rdx");
var = ((unsigned long)hi << 32) | lo;
return (var);
}
/*see https://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html
*/
cntxtSwtchr.c:
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "rdtscp.h"
int main() {
int pipe1[2], pipe2[2];
pipe(pipe1) || pipe(pipe2);
cpu_set_t set;
CPU_ZERO(&set);
clock_t tick, tock;
int fork_rtn;
if ((fork_rtn = fork()) < 0)
exit(1);
if (fork_rtn == 0) { // Child
close(pipe1[1]);
close(pipe2[0]);
CPU_SET(1, &set);
sched_setaffinity(0, sizeof(set), &set);
tick = clock();
unsigned long tsc_start = rdtscp_start();
int i;
while (read(pipe1[0], &i, 4))
write(pipe2[1], &i, 4);
printf("child tsc_ticks: %lu\n", rdtscp_end() - tsc_start);
tock = clock();
clock_t ticks = tock - tick;
double dt = (double)ticks / CLOCKS_PER_SEC;
printf("Elapsed child cpu time: %gs.\n", dt);
close(pipe1[0]);
close(pipe2[1]);
exit(0);
} else { // Parent
close(pipe1[0]);
close(pipe2[1]);
CPU_SET(1, &set);
sched_setaffinity(0, sizeof(set), &set);
int idx, lim = 1000000;
int i_rtnd;
tick = clock();
unsigned long tsc_start = rdtscp_start();
for (idx = 0; idx < lim; ++idx) {
write(pipe1[1], &idx, 4);
read(pipe2[0], &i_rtnd, 4);
if (i_rtnd != idx)
break;
}
printf("parent tsc_ticks: %lu\n", rdtscp_end() - tsc_start);
tock = clock();
clock_t ticks = tock - tick;
double dt = (double)ticks / CLOCKS_PER_SEC;
printf("Elapsed parent cpu time: %gs, %gs/switch.\n", dt, dt / lim);
if (idx == lim)
printf("Parent reached end of processing loop.\n");
else
printf("Parent failed to reach end of processing loop.\n");
close(pipe1[1]);
close(pipe2[0]);
exit(0);
}
}
我正在尝试编写一个程序来测量上下文切换。我已经完成了关于 rdtsc + rdtscp 指令的Intel's manual。
现在,我想跨上下文切换使用这些时间戳指令。我的大致骨架如下:
// init two pipes P1, P2
fork();
set_affinity(); // same core
// parent's code:
cpuid + rdtsc // start timer
write(timer to P1);
read(timer from P2); // blocks parent if timer value not written
rdtscp + cpuid // stop timer, get difference
// child's code:
read(timer from P1); // blocks child if timer value not written
rdtscp + cpuid // stop timer, get difference
cpuid + rdtsc // start timer
write(timer to P2);
我发现这段代码存在一些问题。假设计时器操作正确,
如果 OS 选择上下文切换到某个完全不同的进程(不是子进程或父进程),它将不起作用。
此代码还将包括 read() 和 write() 系统调用所用的时间。
忽略这些问题,是否有效使用 rdtsc + rdtscp 指令?
I know writing a kernel module and disabling preemption/interrupts is a better way
我以前做过,它似乎是测量上下文切换时间的有效方法。每当对这种细粒度的事情进行计时时,调度的不可预测性总是会发挥作用;通常,您通过测量数千次并寻找诸如最小时间间隔、媒体时间间隔或平均时间间隔之类的数字来处理这个问题。您可以通过 运行 使两个进程具有实时 SCHED_FIFO
优先级来减少调度问题。如果您想知道实际的 切换 时间(在单个 cpu 核心上),您需要将两个进程绑定到具有亲和力设置的单个 cpu。如果您只想知道一个进程能够响应另一个进程的输出的延迟,让它们 运行 在不同的 cpu 上就可以了。
另一个要记住的问题是,自愿和非自愿的上下文切换,以及从 user-space 和 kernel-space 开始的切换有不同的成本。你的很可能是自愿的。非自愿测量更难,需要从繁忙的循环或类似的情况中查看共享内存。
我使用了类似的计时代码,除了我有 1000000 次父循环,并在父子循环中为整个循环计时。附上代码。然后我修改它以计时各个上下文切换,就像在您的伪代码中一样,将 1000000 个单独时间相加并与我的原始代码非常一致。因此,考虑到已经提到的注意事项,任何一种方式似乎都有效。
我觉得有趣的是,当使用 sched_setaffinity()
将父项和子项设置为 运行 时,上下文切换时间增加了一倍多 cpus。为什么这会以这种方式影响时间?在同一 cpu 上进程 运行ning 之间的管道是否更快?
rdtscp.h:
static inline unsigned long rdtscp_start(void) {
unsigned long var;
unsigned int hi, lo;
__asm volatile ("cpuid\n\t"
"rdtsc\n\t" : "=a" (lo), "=d" (hi)
:: "%rbx", "%rcx");
var = ((unsigned long)hi << 32) | lo;
return (var);
}
static inline unsigned long rdtscp_end(void) {
unsigned long var;
unsigned int hi, lo;
__asm volatile ("rdtscp\n\t"
"mov %%edx, %1\n\t"
"mov %%eax, %0\n\t"
"cpuid\n\t" : "=r" (lo), "=r" (hi)
:: "%rax", "%rbx", "%rcx", "%rdx");
var = ((unsigned long)hi << 32) | lo;
return (var);
}
/*see https://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html
*/
cntxtSwtchr.c:
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "rdtscp.h"
int main() {
int pipe1[2], pipe2[2];
pipe(pipe1) || pipe(pipe2);
cpu_set_t set;
CPU_ZERO(&set);
clock_t tick, tock;
int fork_rtn;
if ((fork_rtn = fork()) < 0)
exit(1);
if (fork_rtn == 0) { // Child
close(pipe1[1]);
close(pipe2[0]);
CPU_SET(1, &set);
sched_setaffinity(0, sizeof(set), &set);
tick = clock();
unsigned long tsc_start = rdtscp_start();
int i;
while (read(pipe1[0], &i, 4))
write(pipe2[1], &i, 4);
printf("child tsc_ticks: %lu\n", rdtscp_end() - tsc_start);
tock = clock();
clock_t ticks = tock - tick;
double dt = (double)ticks / CLOCKS_PER_SEC;
printf("Elapsed child cpu time: %gs.\n", dt);
close(pipe1[0]);
close(pipe2[1]);
exit(0);
} else { // Parent
close(pipe1[0]);
close(pipe2[1]);
CPU_SET(1, &set);
sched_setaffinity(0, sizeof(set), &set);
int idx, lim = 1000000;
int i_rtnd;
tick = clock();
unsigned long tsc_start = rdtscp_start();
for (idx = 0; idx < lim; ++idx) {
write(pipe1[1], &idx, 4);
read(pipe2[0], &i_rtnd, 4);
if (i_rtnd != idx)
break;
}
printf("parent tsc_ticks: %lu\n", rdtscp_end() - tsc_start);
tock = clock();
clock_t ticks = tock - tick;
double dt = (double)ticks / CLOCKS_PER_SEC;
printf("Elapsed parent cpu time: %gs, %gs/switch.\n", dt, dt / lim);
if (idx == lim)
printf("Parent reached end of processing loop.\n");
else
printf("Parent failed to reach end of processing loop.\n");
close(pipe1[1]);
close(pipe2[0]);
exit(0);
}
}