2017-04-19 1 views
1

Ich versuche, ein Programm zum Messen von Kontextwechsel zu schreiben. Ich habe diese Intel's manual über Rdtsc + Rdtscp Anweisungen durchlaufen.Verwenden von RDTSC + Rdtscp über Kontextwechsel

Jetzt möchte ich diese Zeitstempel Anweisungen über Kontextwechsel verwenden. Ich habe das allgemeine Skelett wie folgt:

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

Es gibt ein paar Probleme, die ich mit diesem Code sehe. Unter der Annahme, dass die Timer-Operationen korrekt sind,

Wenn das Betriebssystem Kontextwechsel zu einem völlig anderen Prozess (nicht der Kind oder Eltern) wählt, wird es nicht funktionieren.

Dieser Code enthält auch die Zeit, die die Systemaufrufe read() und write() benötigen.

Ignorieren diese Probleme, ist es eine gültige Verwendung von Rdtsc + Rdtscp Anweisungen?

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

Antwort

1

ich dies habe zuvor getan, und es scheint eine gültige Methode zur Messung des Kontextschalter Zeit. Wann immer man das Timing von etwas tut, wird diese feinkörnige, planerische Unberechenbarkeit immer ins Spiel kommen; Normalerweise behandeln Sie das, indem Sie Tausende Male messen und nach Zahlen wie dem Minimum, dem Medium oder dem mittleren Zeitintervall suchen. Sie können die Planung eines Problems vereinfachen, indem Sie beide Prozesse mit der Priorität SCHED_FIFO in Echtzeit ausführen. Wenn Sie wissen möchten, Wechsel Zeit (auf einem einzigen CPU-Kern) müssen Sie beide Prozesse an eine einzige CPU mit Affinität Einstellungen binden. Wenn Sie nur die Latenz für einen Prozess wissen wollen, der auf die Ausgabe eines anderen Prozesses reagieren kann, ist es in Ordnung, sie auf verschiedenen CPUs laufen zu lassen.

Ein weiteres Problem zu beachten ist, dass freiwillige und unfreiwillige Kontextwechsel und Switches ausgehend von User-Space und Kernel-Space unterschiedliche Kosten haben. Deines ist wahrscheinlich freiwillig. Das unfreiwillige Messen ist schwieriger und erfordert ein Stöpseln im geteilten Speicher von ausgelasteten Schleifen oder ähnlichem.

+0

ich es mit dem 'Zeit -V' Befehl leite für freiwillige/unfreiwillige Schalter zu überprüfen. Bis jetzt sind die unfreiwilligen Schalter sehr minimal (<0,1%). Die Zeit für den Kontextwechsel beträgt ungefähr 30K Zyklen/12,5 us. –

0

Ich verwendete einen ähnlichen Timing-Code, außer dass ich die Elternschleife 1000000 mal habe, und Zeit die gesamte Schleife in den Eltern und Kind. Der Code ist angehängt. Dann änderte ich es so, dass der individuelle Kontext wechselte, wie in deinem Pseudo-Code, summierte die 1000000 einzelnen Male und bekam gute Übereinstimmung mit meinem ursprünglichen Code. So oder so scheint es zu funktionieren, angesichts der bereits erwähnten Vorbehalte.

Die Sache, die ich interessant finde, ist, dass die Kontextumschaltzeit mehr als verdoppelt wird, wenn verwendet wird, um das Eltern- und das Kind zu setzen, um auf unterschiedlichen CPUs zu laufen. Warum beeinflusst das die Zeit auf diese Weise? Ist die Pipe schneller zwischen Prozessen, die auf derselben CPU laufen?

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

} 
Verwandte Themen