Comment puis-je améliorer / remplacer sprintf, que j'ai mesuré comme étant un point chaud de la performance?

StackOverflow https://stackoverflow.com/questions/271971

Question

Grâce au profilage, j'ai découvert que le sprintf prend beaucoup de temps. Existe-t-il une alternative plus performante qui gère toujours les zéros en tête dans les champs y / m / j h / m / s?

SYSTEMTIME sysTime;
GetLocalTime( &sysTime );
char buf[80];
for (int i = 0; i < 100000; i++)
{

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d",
        sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
        sysTime.wHour, sysTime.wMinute, sysTime.wSecond);

}

Remarque: Le PO explique dans les commentaires qu'il s'agit d'un exemple simplifié. Le " réel " boucle contient du code supplémentaire qui utilise différentes valeurs de temps d'une base de données. Le profilage a identifié sprintf () en tant que contrevenant.

Était-ce utile?

La solution

Si vous écriviez votre propre fonction pour effectuer le travail, une table de correspondance des valeurs de chaîne de 0 à 61 éviterait de devoir effectuer des opérations arithmétiques pour tout ce qui est en dehors de l'année.

modifier: Notez que pour faire face aux secondes intercalaires (et pour correspondre à strftime () ), vous devriez pouvoir imprimer des valeurs de secondes de 60 et 61.

char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" };

Sinon, que diriez-vous de strftime () ? Je ne sais pas du tout à quel point la performance est comparable (elle pourrait simplement appeler sprintf ()), mais cela vaut la peine de regarder (et cela pourrait également être la recherche ci-dessus).

Autres conseils

Vous pouvez essayer de remplir chaque caractère de la sortie à tour de rôle.

buf[0] = (sysTime.wYear / 1000) % 10 + '0' ;
buf[1] = (sysTime.wYear / 100) % 10 + '0';
buf[2] = (sysTime.wYear / 10) % 10 + '0';
buf[3] = sysTime.wYear % 10 + '0';
buf[4] = '-';

... etc ...

Pas joli, mais vous obtenez l'image. Si rien d’autre, cela peut aider à expliquer pourquoi sprintf ne va pas être aussi rapide.

OTOH, vous pourriez peut-être mettre en cache le dernier résultat. De cette façon, vous n’auriez besoin que d’en générer un toutes les secondes.

Printf doit gérer de nombreux formats différents. Vous pouvez certainement récupérer la source de printf et l'utiliser comme base votre propre version qui traite spécifiquement de la structure sysTime . De cette façon, vous ne transmettez qu'un seul argument, qui fait exactement le travail à faire et rien de plus.

Qu'entendez-vous par un "long"? time - puisque sprintf () est la seule instruction de votre boucle et que le paramètre " plumbing " de la boucle (incrément, comparaison) est négligeable, le sprintf () doit consommer le plus de temps.

Vous souvenez-vous de la vieille blague sur l'homme qui a perdu son alliance dans la 3ème rue une nuit, mais l'a recherchée le 5 parce que la lumière y était plus brillante? Vous avez créé un exemple conçu pour "prouver" votre hypothèse selon laquelle sprintf () est inefficace.

Vos résultats seront plus précis si vous définissez le profil " réel " code contenant sprintf () en plus de toutes les autres fonctions et algorithmes que vous utilisez. Vous pouvez également essayer d’écrire votre propre version en fonction de la conversion numérique spécifique sans remplissage dont vous avez besoin.

Vous pourriez être surpris des résultats.

On dirait que Jaywalker suggère une méthode très similaire (me battre de moins d’une heure).

En plus de la méthode de table de consultation déjà suggérée (tableau n2s [] ci-dessous), pourquoi ne pas générer votre tampon de format afin que le sprintf habituel soit moins intensif? Le code ci-dessous ne devra renseigner que les minutes et les secondes à chaque fois dans la boucle, sauf si l'année / mois / jour / heure a changé. Évidemment, si l'un de ceux-ci a changé, vous subissez un autre impact sur le sprintf, mais dans l'ensemble, il se peut que ce ne soit pas plus que ce que vous êtes en train d'assister (combiné avec la recherche de tableau).


static char fbuf[80];
static SYSTEMTIME lastSysTime = {0, ..., 0};  // initialize to all zeros.

for (int i = 0; i < 100000; i++)
{
    if ((lastSysTime.wHour != sysTime.wHour)
    ||  (lastSysTime.wDay != sysTime.wDay)
    ||  (lastSysTime.wMonth != sysTime.wMonth)
    ||  (lastSysTime.wYear != sysTime.wYear))
    {
        sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s",
                sysTime.wYear, n2s[sysTime.wMonth],
                n2s[sysTime.wDay], n2s[sysTime.wHour]);

        lastSysTime.wHour = sysTime.wHour;
        lastSysTime.wDay = sysTime.wDay;
        lastSysTime.wMonth = sysTime.wMonth;
        lastSysTime.wYear = sysTime.wYear;
    }

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]);

}

Qu'en est-il de la mise en cache des résultats? N'est-ce pas une possibilité? Considérant que cet appel sprintf () particulier est fait trop souvent dans votre code, je suppose que, entre la plupart de ces appels consécutifs, l'année, le mois et le jour ne changent pas.

Ainsi, nous pouvons implémenter quelque chose comme ce qui suit. Déclarez une structure SYSTEMTIME ancienne et une structure actuelle:

SYSTEMTIME sysTime, oldSysTime;

De plus, déclarez que des pièces séparées doivent contenir la date et l'heure:

char datePart[80];
char timePart[80];

Pour la première fois, vous devrez renseigner sysTime, oldSysTime ainsi que datePart et timePart. Mais les sprintf () suivants peuvent être rendus assez rapidement comme indiqué ci-dessous:

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
if (oldSysTime.wYear == sysTime.wYear && 
  oldSysTime.wMonth == sysTime.wMonth &&
  oldSysTime.wDay == sysTime.wDay) 
  {
     // we can reuse the date part
     strcpy (buff, datePart);
     strcat (buff, timePart);
  }
else {
     // we need to regenerate the date part as well
     sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay);
     strcpy (buff, datePart);
     strcat (buff, timePart);
}

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME));

Le code ci-dessus présente une certaine redondance pour le rendre plus facile à comprendre. Vous pouvez factoriser facilement. Vous pouvez encore accélérer si vous savez que même les heures et les minutes ne changeront pas plus rapidement que votre appel à la routine.

Je ferais quelques petites choses ...

  • cache l'heure actuelle pour ne pas avoir à régénérer l'horodatage à chaque fois
  • faites la conversion du temps manuellement. La partie la plus lente des fonctions printf -family est l'analyse du format, et il est idiot de consacrer des cycles à cet analyse à chaque exécution de boucle.
  • essayez d'utiliser des tables de conversion de 2 octets pour toutes les conversions ( {"00", "01", "02", ..., "99"} ). En effet, vous voulez éviter l’arithmétique moduluar et un tableau de 2 octets signifie que vous n’utilisez qu’un seul modulo pour l’année.

Vous obtiendrez probablement une augmentation manuelle de la routine qui affiche les chiffres dans le retour, car vous pourriez éviter d'analyser de manière répétée une chaîne de format et n'auriez pas à traiter beaucoup de cas plus complexes traités par sprintf. . Je ne voudrais pas vraiment recommander cela.

Je vous recommanderais d'essayer de déterminer si vous pouvez réduire d'une manière ou d'une autre la quantité dont vous avez besoin pour générer ces chaînes, sont-elles facultatives à certains moments, peuvent-elles être mises en cache, etc.?

Je travaille actuellement sur un problème similaire.

Je dois consigner les instructions de débogage avec l’horodatage, le nom du fichier, le numéro de ligne, etc. sur un système intégré. Nous avons déjà un enregistreur en place, mais lorsque je tourne le bouton en "consignation complète", il consomme tous nos cycles de traitement et met notre système dans des états extrêmes, déclarant qu'aucun appareil informatique ne devrait jamais avoir à faire l'expérience.

Quelqu'un a dit "Vous ne pouvez pas mesurer / observer quelque chose sans changer ce que vous mesurez / observez."

Donc, je change les choses pour améliorer les performances. L'état actuel des choses est qu'Im 2x plus rapide que l'appel de fonction d'origine (le goulot d'étranglement dans ce système de journalisation ne se trouve pas dans l'appel de fonction mais dans le lecteur de journaux qui est un exécutable séparé, ce que je peux ignorer. si j’écris ma propre pile de journalisation).

L'interface que je dois fournir est quelque chose comme- journal vide (canal int, caractère * nom de fichier, int lineno, format, ...) . Je dois ajouter le nom du canal (qui fait actuellement une recherche linéaire dans une liste! Pour chaque instruction de débogage!) Et un horodatage comprenant un compteur de millisecondes. Voici quelques-unes des choses que je fais pour accélérer les choses -

  • Chaîne le nom du canal afin que je puisse strcpy plutôt que de rechercher dans la liste. définir la macro LOG (canal, ... etc) en tant que log (#channel, ... etc) . Vous pouvez utiliser memcpy si vous corrigez la longueur de la chaîne en définissant LOG (canal, ...) log ("quot ...") # channel - sizeof ("...." #channel) + * 11 *) pour obtenir des longueurs de canal fixes 10
  • Générez une chaîne d'horodatage plusieurs fois par seconde. Vous pouvez utiliser asctime ou quelque chose. Puis mémorisez la chaîne de longueur fixe à chaque instruction de débogage.
  • Si vous souhaitez générer la chaîne d’horodatage en temps réel, une table de correspondance avec affectation (et non pas memcpy!) est parfaite. Mais cela ne fonctionne que pour les nombres à 2 chiffres et peut-être pour l'année.
  • Qu'en est-il de trois chiffres (millisecondes) et cinq chiffres (lineno)? Je n'aime pas les itoa et je n'aime pas les itoa personnalisés ( digit = ((valeur / = valeur)% 10) ), soit parce que les divs et les mods sont lents . J'ai écrit les fonctions ci-dessous et j'ai découvert par la suite que quelque chose de similaire se trouvait dans le manuel d'optimisation d'AMD (en assemblage), ce qui me donnait l'assurance qu'il s'agissait des implémentations C les plus rapides.

    void itoa03(char *string, unsigned int value)
    {
       *string++ = '0' + ((value = value * 2684355) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

    De même, pour les numéros de ligne,

    void itoa05(char *string, unsigned int value)
    {
       *string++ = ' ';
       *string++ = '0' + ((value = value * 26844 + 12) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

Globalement, mon code est assez rapide maintenant. Le vsnprintf () que je dois utiliser prend environ 91% du temps et le reste de mon code, seulement 9% (alors que le reste du code, c’est-à-dire sauf vsprintf () l'habitude de prendre 54% plus tôt)

Les deux outils de formatage rapide que j'ai testés sont FastFormat et Karma :: generate (élément de Stimuler l'esprit ).

Vous pouvez également trouver utile de le comparer ou au moins de rechercher des critères existants.

Par exemple, celui-ci (bien qu'il manque FastFormat):

 Conversion d'entier rapide en chaîne en C ++

StringStream est une suggestion de Google.

http://bytes.com/forum/thread132583.html

Il est difficile d’imaginer que vous allez battre sprintf au formatage des nombres entiers. Êtes-vous sûr que votre problème est le sprintf?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top