2017-04-19 171 views
1

我正在嘗試編寫一個程序來測量上下文切換。關於rdtsc + rdtscp指令我已經通過了這個Intel's manual跨上下文切換使用rdtsc + rdtscp

現在,我想通過上下文切換使用這些時間戳指令。我有一般的骨架如下:

// 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); 

我看到這個代碼有幾個問題。假設定時器的操作是正確的,那麼

如果操作系統選擇上下文切換到某個完全不同的進程(而不是子進程或父進程),那麼它將不起作用。

此代碼還將包含read()和write()系統調用所花費的時間。

忽略這些問題,是否有效使用rdtsc + rdtscp指令?

I know writing a kernel module and disabling preemption/interrupts is a better way 

回答

1

我以前做過這件事,它似乎是測量上下文切換時間的有效方法。無論何時對這些細節進行時間安排,調度不可預測性總是會發揮作用;通常你通過測量數千次來處理這個問題,並尋找像最小值,媒體或平均時間間隔這樣的數字。通過以實時SCHED_FIFO優先級運行這兩個進程,您可以使調度更少。如果您想知道實際的切換時間(在單個CPU核心上),您需要將兩個進程綁定到具有親和性設置的單個CPU。如果你只是想知道一個進程能夠響應另一個進程的延遲,讓它們運行在不同的cpus上就沒有問題。

另一個要記住的問題是自願和非自願的上下文切換,並且從用戶空間開始與從內核空間開始的切換具有不同的成本。你的可能是自願的。無意識測量比較困難,需要從繁忙循環或類似環境中分享共享內存。

+0

我用'時間-v'命令檢查自願/非自願開關運行它。到目前爲止,無意識開關非常小(<0.1%)。上下文切換時間約爲30K週期/12.5秒。 –

0

我使用了一個類似的計時代碼,除了我有父循環1000000次,並在父母和孩子的整個循環時間。該代碼已附加。然後,我修改它以定時單個上下文切換,就像在你的僞代碼中一樣,總結了1000000個單獨的時間,並與我的原始代碼達成了很好的一致。所以無論哪種方式似乎都有效,因爲已經提到了一些警告。

我覺得有趣的是,當使用sched_setaffinity()來設置父級和子級別在單獨的cpus上運行時,上下文切換時間增加了一倍多。爲什麼這樣會影響時間?在同一cpu上運行的進程之間管道是否更快?

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); 
    } 

}