web-dev-qa-db-fra.com

équivalent plus rapide de gettimeofday

En essayant de construire une application très sensible à la latence, qui doit envoyer des centaines de messages par seconde, chaque message ayant le champ d'heure, nous avons voulu envisager d'optimiser gettimeofday. Notre première pensée a été l'optimisation basée sur rdtsc. Des pensées ? D'autres pointeurs? La précision requise de la valeur de temps renvoyée est en millisecondes, mais ce n'est pas grave si la valeur est parfois désynchronisée avec le récepteur pendant 1-2 millisecondes. Essayer de faire mieux que les 62 nanosecondes que prend gettimeofday

26
Humble Debugger

Avez-vous réellement effectué un benchmark et constaté que gettimeofday était trop lent?

Au rythme de 100 messages par seconde, vous disposez de 10 ms de temps processeur par message. Si vous avez plusieurs cœurs, en supposant qu'il puisse être entièrement parallélisé, vous pouvez facilement l'augmenter de 4 à 6 fois - soit 40 à 60 ms par message! Il est peu probable que le coût de gettimeofday soit proche de 10 ms - je soupçonne que cela ressemble davantage à 1 à 10 microsecondes (sur mon système, le micro-benchmarking donne environ 1 microseconde par appel - essayez par vous-même ). Vos efforts d'optimisation seraient mieux dépensés ailleurs.

Bien que l'utilisation du TSC soit une idée raisonnable, Linux moderne a déjà un espace utilisateur basé sur TSC gettimeofday - lorsque cela est possible, le vdso tirera une implémentation de gettimeofday qui applique un décalage (lu à partir d'un noyau partagé- segment de mémoire utilisateur) à la valeur de rdtsc, calculant ainsi l'heure sans entrer dans le noyau. Cependant, certains modèles de CPU n'ont pas de TSC synchronisé entre différents cœurs ou différents packages, et cela peut donc finir par être désactivé. Si vous souhaitez une synchronisation de haute performance, vous pouvez d'abord envisager de trouver un modèle de processeur doté d'un TSC synchronisé.

Cela dit, si vous êtes prêt à sacrifier une quantité importante de résolution (votre timing ne sera précis que jusqu'au dernier tick, ce qui signifie qu'il pourrait être désactivé par dizaines de millisecondes), vous pouvez utiliser CLOCK_MONOTONIC_COARSE ou CLOCK_REALTIME_COARSE = avec clock_gettime . Ceci est également implémenté avec le vdso, et garanti de ne pas appeler dans le noyau (pour les noyaux récents et la glibc).

45
bdonlan

Horloges POSIX

J'ai écrit une référence pour les sources d'horloge POSIX:

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

Ces chiffres proviennent d'un processeur Intel Core i7-4771 à 3,50 GHz sur Linux 4.0. Ces mesures ont été prises en utilisant le registre TSC et en exécutant chaque méthode d'horloge des milliers de fois et en prenant la valeur de coût minimum.

Vous voudrez bien tester sur les machines sur lesquelles vous comptez exécuter car la façon dont celles-ci sont implémentées varie selon la version du matériel et du noyau. Le code peut être trouvé ici . Il s'appuie sur le registre TSC pour le comptage de cycles, qui se trouve dans le même référentiel ( tsc.h ).

TSC

Accéder au TSC (compteur d'horodatage du processeur) est le moyen le plus précis et le moins cher de chronométrer les choses. Généralement, c'est ce que le noyau utilise lui-même. Il est également assez simple sur les puces Intel modernes, car le TSC est synchronisé entre les cœurs et n'est pas affecté par la mise à l'échelle des fréquences. Il fournit donc une source de temps globale simple. Vous pouvez voir un exemple d'utilisation ici avec une procédure pas à pas du code d'assemblage ici .

Le principal problème avec cela (autre que la portabilité) est qu'il ne semble pas y avoir de bonne façon de passer des cycles aux nanosecondes. Pour autant que je sache, les documents Intel indiquent que le TSC fonctionne à une fréquence fixe, mais que cette fréquence peut différer de la fréquence indiquée par les processeurs. Intel ne semble pas fournir un moyen fiable de déterminer la fréquence TSC. Le noyau Linux semble résoudre ce problème en testant le nombre de cycles TSC se produisant entre deux temporisateurs matériels (voir ici ).

Memcached

Memcached dérange pour faire la méthode du cache. Il peut simplement s'agir de vous assurer que les performances sont plus prévisibles sur toutes les plates-formes ou qu'elles évoluent mieux avec plusieurs cœurs. Cela peut également ne pas être une optimisation intéressante.

52
David Terei

Comme le dit bdonian, si vous n'envoyez que quelques centaines de messages par seconde, gettimeofday va être assez rapide.

Cependant, si vous envoyez des millions de messages par seconde, cela peut être différent (mais vous devez quand même mesurer qu'il s'agit d'un goulot d'étranglement). Dans ce cas, vous voudrez peut-être envisager quelque chose comme ceci:

  • avoir une variable globale, donnant l'horodatage actuel avec la précision souhaitée
  • avoir un thread d'arrière-plan dédié qui ne fait que mettre à jour l'horodatage (si l'horodatage doit être mis à jour toutes les T unités de temps, alors le thread doit dormir une fraction de T, puis mettre à jour l'horodatage; utilisez des fonctionnalités en temps réel si vous en avez besoin)
  • tous les autres threads (ou le processus principal, si vous n'utilisez pas de threads autrement) lit simplement la variable globale

Le langage C ne garantit pas que vous pouvez lire la valeur d'horodatage si elle est supérieure à sig_atomic_t. Vous pouvez utiliser le verrouillage pour y faire face, mais le verrouillage est lourd. À la place, vous pouvez utiliser un volatile sig_atomic_t variable typée pour indexer un tableau d'horodatages: le thread d'arrière-plan met à jour l'élément suivant du tableau, puis met à jour l'index. Les autres threads lisent l'index, puis lisent le tableau: ils peuvent obtenir un minuscule horodatage obsolète (mais ils obtiennent le bon la prochaine fois), mais ils ne rencontrent pas le problème où ils lisent l'horodatage à en même temps, il est mis à jour et récupère quelques octets de l'ancienne valeur et une partie de la nouvelle valeur.

Mais tout cela est beaucoup trop pour seulement des centaines de messages par seconde.

4
user25148

Vous trouverez ci-dessous une référence. Je vois environ 30ns. printTime () de rashad Comment obtenir l'heure et la date actuelles en 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 sec pour 100 000 000 d'appels ou 30 ns;

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

Avez-vous besoin de la précision en millisecondes? Sinon, vous pouvez simplement utiliser time() et gérer l'horodatage unix.

0
Vinicius Kamakura