2016-06-08 20 views
1

Ich mache einige Experimente mit Low-Latency-Programmierung. Ich möchte den Kontextwechsel ausschalten und Latenz zuverlässig messen können, ohne die Leistung zu sehr zu beeinträchtigen.Warum seltsame Timing-Ergebnisse angezeigt werden, obwohl ein Thread an einen bestimmten CPU-Kern gebunden ist?

Zu Beginn schrieb ich ein Programm, das Zeit in Schleife 1M Zeiten anfordert und dann Statistiken (Code unten) ausdruckt, da ich wissen wollte, wie viel Zeit der Anruf an den Timer nimmt. Überraschenderweise ist die Ausgabe der folgenden (in Mikrosekunden) ist:

Mean: 0.59, Min: 0.49, Max: 25.77 
Mean: 0.59, Min: 0.49, Max: 11.73 
Mean: 0.59, Min: 0.42, Max: 14.11 
Mean: 0.59, Min: 0.42, Max: 13.34 
Mean: 0.59, Min: 0.49, Max: 11.45 
Mean: 0.59, Min: 0.42, Max: 14.25 
Mean: 0.59, Min: 0.49, Max: 11.80 
Mean: 0.59, Min: 0.42, Max: 12.08 
Mean: 0.59, Min: 0.49, Max: 21.02 
Mean: 0.59, Min: 0.42, Max: 12.15 

Wie Sie sehen können, obwohl die durchschnittliche Zeit weniger als eine Mikrosekunde ist, gibt es Spitzen von bis zu 20 Mikrosekunden. Das ist trotz der Tatsache, dass der Code auf einem dedizierten Kern läuft (Affinität für einen bestimmten Kern festgelegt, während die Affinität des Init-Prozesses auf eine Gruppe von anderen Kernen festgelegt ist), und dass Hyper-Threading auf dem Computer deaktiviert ist. Versucht es mit mehreren Kernel-Versionen, einschließlich preemptive und RT, und die Ergebnisse sind im Wesentlichen die gleichen.

Können Sie den großen Unterschied zwischen Mittelwert und Maximum erklären?
Ist das Problem in den Anrufen an den Timer, oder mit einer Prozess-Isolation?

Ich habe auch versucht, dies mit Anrufen zu anderen Zeiten -
-CLOCK_THREAD_CPUTIME_ID,
- CLOCK_MONOTONIC,
-CLOCK_PROCESS_CPUTIME_ID
- und das beobachtete Muster war die gleiche ...

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

#define min(a, b) ((a) < (b) ? (a) : (b)) 
#define max(a, b) ((a) > (b) ? (a) : (b)) 

uint64_t 
time_now() 
{ 
    struct timespec ts; 
    clock_gettime(CLOCK_REALTIME, &ts); 

    return ts.tv_sec * 1000000000 + ts.tv_nsec; 
} 

void 
set_affinity(int cpu) 
{ 
    cpu_set_t set; 
    CPU_ZERO(&set); 
    CPU_SET(cpu, &set); 

    if (sched_setaffinity(0, sizeof(set), &set)) 
    { 
      perror("sched_setaffinity"); 
    } 
} 


#define NUM 1000000 
#define LOOPS 10 

int 
main(int argc, char **argv) 
{ 
    set_affinity(3); 

    for (int loop = 0; loop < LOOPS; ++ loop) 
    { 
     uint64_t t_0 = time_now(); 

     uint64_t sum_val = 0; 
     uint64_t max_val = 0; 
     uint64_t min_val = uint64_t(-1); 

     for (int k = 0; k < NUM; ++ k) 
     { 
      uint64_t t_1 = time_now(); 
      uint64_t t_diff = t_1 - t_0; 

      sum_val += t_diff; 
      min_val = min(t_diff, min_val); 
      max_val = max(t_diff, max_val); 

      t_0 = t_1; 
     } 

     printf("Mean: %.2f, Min: %.2f, Max: %.2f\n", ((double)sum_val)/NUM/1000, ((double)min_val)/1000, ((double)max_val)/1000); 
    } 

    return 0; 
} 
+0

Es ist besser, asm "rdtsc" Anweisung für genaue Zeitmessung zu verwenden – eungenue

Antwort

0

Zwei Quellen von Unberechenbarkeit werden von Geräteunterbrechungen und Timern sein. Während Sie die Affinität von Userspace-Prozessen festgelegt haben, treten Interrupts von Geräten auf und wirken sich auf Ihren Prozess aus. Der Kernel verwendet auch Timer, die bei periodischen Ticks auftreten, damit er die Zeit verfolgen kann. Ich würde sagen, dass dies die ersten beiden Hauptquellen für Unvorhersehbarkeit sein werden. Interprozess-Interrupts (IPIs), die für die gegenseitige Signalisierung von Kernen verwendet werden, sind ein anderer, aber wahrscheinlich nicht so hoch wie die ersten beiden.