32 Stimmen

schnellere Entsprechung von gettimeofday

Bei dem Versuch, eine sehr latenzempfindliche Anwendung zu erstellen, die 100 Nachrichten pro Sekunde senden muss, wobei jede Nachricht das Zeitfeld enthält, wollten wir eine Optimierung von gettimeofday in Betracht ziehen. Unser erster Gedanke war rdtsc basierte Optimierung. Irgendwelche Ideen? Irgendwelche anderen Hinweise? Die erforderliche Genauigkeit des zurückgegebenen Zeitwerts wird in Millisekunden angegeben, aber es ist keine große Sache, wenn der Wert gelegentlich um 1-2 Millisekunden vom Empfänger abweicht. Versuchen Sie, besser als die 62 Nanosekunden gettimeofday nimmt zu tun

74voto

David Terei Punkte 1907

POSIX-Uhren

Ich habe einen Benchmark für POSIX-Taktquellen geschrieben:

  • Zeit (s) => 3 Zyklen
  • ftime (ms) => 54 Zyklen
  • gettimeofday (us) => 42 Zyklen
  • clock_gettime (ns) => 9 Zyklen (CLOCK_MONOTONIC_COARSE)
  • clock_gettime (ns) => 9 Zyklen (CLOCK_REALTIME_COARSE)
  • clock_gettime (ns) => 42 Zyklen (CLOCK_MONOTONIC)
  • clock_gettime (ns) => 42 Zyklen (CLOCK_REALTIME)
  • clock_gettime (ns) => 173 Zyklen (CLOCK_MONOTONIC_RAW)
  • clock_gettime (ns) => 179 Zyklen (CLOCK_BOOTTIME)
  • clock_gettime (ns) => 349 Zyklen (CLOCK_THREAD_CPUTIME_ID)
  • clock_gettime (ns) => 370 Zyklen (CLOCK_PROCESS_CPUTIME_ID)
  • rdtsc (Zyklen) => 24 Zyklen

Diese Zahlen stammen von einer Intel Core i7-4771 CPU mit 3,50 GHz unter Linux 4.0. Diese Messungen wurden mithilfe des TSC-Registers durchgeführt, wobei jede Taktmethode Tausende Male ausgeführt und der niedrigste Kostenwert ermittelt wurde.

Sie sollten jedoch auf den Rechnern testen, auf denen Sie das Programm ausführen wollen, da die Implementierung dieser Funktionen je nach Hardware und Kernelversion variiert. Der Code kann gefunden werden aquí . Es stützt sich auf das TSC-Register für die Zykluszählung, das sich im gleichen Repo ( tsc.h ).

TSC

Der Zugriff auf den TSC (Prozessor-Zeitstempelzähler) ist die genaueste und billigste Art, die Zeit zu messen. Im Allgemeinen ist es das, was der Kernel selbst verwendet. Auf modernen Intel-Chips ist dies auch recht einfach, da der TSC über alle Kerne hinweg synchronisiert ist und nicht von der Frequenzskalierung beeinflusst wird. Sie bietet also eine einfache, globale Zeitquelle. Sie können ein Beispiel für die Verwendung dieser Methode sehen aquí mit einem Durchgang durch den Assemblercode aquí .

Das Hauptproblem dabei (abgesehen von der Übertragbarkeit) ist, dass es keinen guten Weg zu geben scheint, von Zyklen zu Nanosekunden zu wechseln. Soweit ich in den Intel-Dokumenten finden kann, läuft der TSC mit einer festen Frequenz, aber diese Frequenz kann von der angegebenen Frequenz des Prozessors abweichen. Intel scheint keinen zuverlässigen Weg zu bieten, um die TSC-Frequenz herauszufinden. Der Linux-Kernel scheint dieses Problem zu lösen, indem er prüft, wie viele TSC-Zyklen zwischen zwei Hardware-Timern auftreten (siehe aquí ).

Memcached

Memcached macht sich die Mühe, die Cache-Methode anzuwenden. Das kann einfach daran liegen, dass die Leistung auf verschiedenen Plattformen besser vorhersehbar ist oder dass sie mit mehreren Kernen besser skaliert. Vielleicht ist es auch keine lohnende Optimierung.

51voto

bdonlan Punkte 213545

Haben Sie tatsächlich ein Benchmarking durchgeführt und festgestellt gettimeofday unannehmbar langsam zu sein?

Bei einer Rate von 100 Nachrichten pro Sekunde haben Sie 10 ms CPU-Zeit pro Nachricht. Wenn Sie über mehrere Kerne verfügen und davon ausgehen, dass es vollständig parallelisiert werden kann, können Sie diese Zeit leicht um das 4-6fache erhöhen - das sind 40-60 ms pro Nachricht! Die Kosten für gettimeofday werden wahrscheinlich nicht annähernd 10 ms betragen - ich vermute, dass sie eher bei 1-10 Mikrosekunden liegen (auf meinem System ergibt ein Mikrobenchmarking etwa 1 Mikrosekunde pro Aufruf). Probieren Sie es selbst aus ). Ihre Optimierungsbemühungen wären an anderer Stelle besser aufgehoben.

Während die Verwendung des TSC eine vernünftige Idee ist, hat modernes Linux bereits eine Userspace TSC-basiertes gettimeofday - Wenn möglich, wird das vdso eine Implementierung von gettimeofday einbeziehen, die einen Offset (gelesen aus einem gemeinsamen Kernel-Benutzer-Speicher-Segment) auf rdtsc und berechnet so die Tageszeit, ohne den Kernel zu betreten. Einige CPU-Modelle verfügen jedoch nicht über einen TSC, der zwischen verschiedenen Kernen oder verschiedenen Paketen synchronisiert ist, so dass dies am Ende deaktiviert sein kann. Wenn Sie ein leistungsfähiges Timing wünschen, sollten Sie sich zunächst nach einem CPU-Modell umsehen, das über einen synchronisierten TSC verfügt.

Wenn Sie jedoch bereit sind, ein erhebliches Maß an Auflösung zu opfern (Ihr Timing wird nur auf den letzten Tick genau sein, was bedeutet, dass es um einige Millisekunden abweichen kann), können Sie Folgendes verwenden CLOCK_MONOTONIC_COARSE oder CLOCK_REALTIME_COARSE con clock_gettime . Dies ist auch mit dem vdso implementiert, und garantiert nicht in den Kernel (für aktuelle Kernel und glibc) aufrufen.

5voto

Wie bdonian sagt, wenn Sie nur ein paar hundert Nachrichten pro Sekunde versenden, gettimeofday schnell genug sein wird.

Wenn Sie jedoch Millionen von Nachrichten pro Sekunde versenden würden, könnte es anders sein (aber Sie sollten trotzdem Maßnahme dass es ein Engpass ist). In diesem Fall sollten Sie etwas wie dieses in Betracht ziehen:

  • eine globale Variable haben, die den aktuellen Zeitstempel mit der gewünschten Genauigkeit angibt
  • einen eigenen Hintergrund-Thread haben, der nichts anderes tut, als den Zeitstempel zu aktualisieren (wenn der Zeitstempel alle T Zeiteinheiten aktualisiert werden soll, dann lassen Sie den Thread einen Bruchteil von T schlafen und aktualisieren Sie dann den Zeitstempel; verwenden Sie Echtzeitfunktionen, wenn es nötig ist)
  • alle anderen Threads (oder der Hauptprozess, wenn Sie keine anderen Threads verwenden) lesen einfach die globale Variable

Die Sprache C garantiert nicht, dass Sie den Zeitstempelwert lesen können, wenn er größer ist als sig_atomic_t . Sie könnten dafür eine Sperre verwenden, aber die ist schwer zu handhaben. Stattdessen könnten Sie eine volatile sig_atomic_t typisierte Variable, um ein Array von Zeitstempeln zu indizieren: der Hintergrund-Thread aktualisiert das nächste Element im Array und dann den Index. Die anderen Threads lesen den Index und dann das Array: Sie erhalten möglicherweise einen winzigen veralteten Zeitstempel (aber sie erhalten beim nächsten Mal den richtigen), aber sie haben nicht das Problem, dass sie den Zeitstempel zur gleichen Zeit lesen, zu der er aktualisiert wird, und einige Bytes des alten Wertes und einige des neuen Wertes erhalten.

Aber all dies ist ein Overkill für nur Hunderte von Nachrichten pro Sekunde.

1voto

edW Punkte 1839

Nachstehend finden Sie einen Vergleichswert. Ich sehe etwa 30ns. printTime() von rashad Wie erhält man die aktuelle Zeit und das Datum in C++?

#include <string>
#include <iostream>
#include <sys/time.h>
using namespace std;

void printTime(time_t now)
{
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
    cout << buf << endl;
}

int main()
{
   timeval tv;
   time_t tm;

   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);
   for(int i=0; i<100000000; i++)
        gettimeofday(&tv,NULL);
   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);

   printTime(time(NULL));
   for(int i=0; i<100000000; i++)
        tm=time(NULL);
   printTime(time(NULL));

   return 0;
}

3 Sekunden für 100.000.000 Anrufe oder 30ns;

2014-03-20.09:23:35
2014-03-20.09:23:38
2014-03-20.09:23:38
2014-03-20.09:23:41

0voto

Vinicius Kamakura Punkte 7548

Benötigen Sie die Millisekundengenauigkeit? Wenn nicht, können Sie einfach Folgendes verwenden time() und mit dem Unix-Zeitstempel umgehen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X