如何更准确地衡量上下文切换的成本

how to measure the cost of context switching more precisely

我正在编写一些代码来粗略衡量上下文切换的成本。基本思想在课本里面OSTEP。基于这个想法,我写了一些代码如下:

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 199309L

#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>
#include <time.h>

#define TIMES 1000
#define BILLION 10e9

int main(int argc, char *argv[]) {
    int pipefd_1[2], pipefd_2[2];

    struct timespec start, stop;
    clockid_t clk_id = CLOCK_REALTIME;

    // for child and parent process run on the same cpu
    cpu_set_t set;
    int parentCPU, childCPU;

    char testChar = 'a';        /* Use for test */

    if (argc != 3) {
        fprintf(stderr, "Usage: %s parent-cpu child-cpu\n",
                argv[0]);
        exit(EXIT_FAILURE);
    }

    parentCPU = atoi(argv[1]);
    childCPU = atoi(argv[2]);

    CPU_ZERO(&set);

    if (pipe(pipefd_1) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    if (pipe(pipefd_2) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    switch (fork()) {
        case -1:    /* error */
            perror("fork");
            exit(EXIT_FAILURE);
            
        case 0:     /* child process */
            CPU_SET(childCPU, &set);

            if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) {
                perror("set cpu for child process");
                exit(EXIT_FAILURE);
            }

            char readChar_c;

            close(pipefd_1[0]);     /* Close unused read end */
            close(pipefd_2[1]);     /* Close unused write end */

            for (int i = 0; i < TIMES; ++i) {
                while (read(pipefd_2[0], &readChar_c, 1) <= 0) {}       /* read to the first pipe */
                write(pipefd_1[1], &readChar_c, 1);                     /* write to the first pipe */
            }

            close(pipefd_2[0]);
            close(pipefd_1[1]);

            exit(EXIT_SUCCESS);

        default:    /* parent process */
            CPU_SET(parentCPU, &set);

            if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) {
                perror("set cpu for parent process");
                exit(EXIT_FAILURE);
            }

            char readChar_p;

            close(pipefd_2[0]);     /* Close unused read end */
            close(pipefd_1[1]);     /* Close unused write end */
            
            clock_gettime(clk_id, &start);
            for (int i = 0; i < TIMES; ++i) {
                write(pipefd_2[1], &testChar, 1);                   /* write to the second pipe */
                while (read(pipefd_1[0], &readChar_p, 1) <= 0) {}   /* read to the first pipe */
            }
            clock_gettime(clk_id, &stop);

            close(pipefd_2[1]);
            close(pipefd_1[0]);

            printf("the average cost of context switching is: %lf nsec\n", ((stop.tv_sec - start.tv_sec) * BILLION
                         + stop.tv_nsec - start.tv_nsec) / TIMES);
    }

    exit(EXIT_SUCCESS);
}

但是我对这个问题还有一些疑问。

  1. 我读过别人的代码,他们只是使用 read(pipefd_2[0], NULL, 0) 和 write(pipefd_1[1], NULL, 0) 执行读写操作。不知道如果你在一个进程中还没有向pipe1写入一些数据,而你想在另一个进程中通过pipe1读取数据,这种情况下会不会发生上下文切换?或只读取函数 return 0?

  2. 由于通过pipe读取会发生上下文切换,准确的上下文切换开销应该是从离开那个进程到进入另一个进程的开销,不包括后面进程执行某些指令的时间,所以我觉得用这种方式来计算上下文切换的代价可能不够精确。这是因为与切换上下文相比,执行时间可以忽略不计吗?

感谢您的帮助!

  1. #define BILLION 1e9 //not 10e9

  2. 代码没问题。 read() not return 0 如果管道中没有数据,它会阻塞。

    这就是为什么你打的乒乓球能有效衡量成本的原因 上下文切换(+IO 开销)。

    read() returns 仅当所有 OS 计数的引用(创建 通过 dup* 函数或 forking 结合 fd 继承) 到对应的写端都关闭了。

  3. 您正在有效地测量上下文切换 + 管道的 IO 开销。您可以通过调整代码以在 >=2 核心系统上仅使用一个管道(因此每个 io 调用几乎没有上下文切换)并使一个进程成为永久进程 reader 来单独测量管道的近似 IO 开销另一个是永久作家 (https://pastebin.com/cGDWFdgQ)。我得到大约 2*0.55µs 的开销 + 整个事情大约 5.5µs,所以每次上下文切换大约 4.4µs。