2009-07-24 5 views
17

Dans mon binaire en texte l'application de décodage (.NET 2.0) je trouve que la ligne:Comment améliorer les performances du code avec DateTime.ToString?

logEntryTime.ToString("dd.MM.yy HH:mm:ss:fff") 

prend 33% du temps de traitement total. Est-ce que quelqu'un a des idées sur la façon de le rendre plus rapide?

EDIT: Cette application est utilisée pour traiter certains journaux binaires et cela prend actuellement 15 heures pour s'exécuter. Donc, 1/3 de ce sera 5 heures.

EDIT: J'utilise NProf pour le profilage. L'application traite environ 17 Go de journaux binaires.

+4

33% de quoi? 33% du temps de traitement de 2 ms pourrait même ne pas valoir le refactor. –

+0

En outre, quel outil avez-vous utilisé pour déterminer les 33%? Différents outils mesurent parfois des choses subtilement différentes, donc ça vaut le coup de savoir ... – Stobor

+2

Comment ajouter la chaîne résultante à votre sortie? Stringbuilder? –

Répondre

13

Il est regrettable que .NET ne dispose pas d'un type de "formateur" qui peut analyser un motif et s'en souvenir.

Si vous utilisez toujours le même format, vous pourriez vouloir créer manuellement un formateur pour faire exactement cela. Quelque chose le long des lignes de:

public static string FormatDateTime(DateTime dt) 
{ 
    char[] chars = new char[21]; 
    Write2Chars(chars, 0, dt.Day); 
    chars[2] = '.'; 
    Write2Chars(chars, 3, dt.Month); 
    chars[5] = '.'; 
    Write2Chars(chars, 6, dt.Year % 100); 
    chars[8] = ' '; 
    Write2Chars(chars, 9, dt.Hour); 
    chars[11] = ' '; 
    Write2Chars(chars, 12, dt.Minute); 
    chars[14] = ' '; 
    Write2Chars(chars, 15, dt.Second); 
    chars[17] = ' '; 
    Write2Chars(chars, 18, dt.Millisecond/10); 
    chars[20] = Digit(dt.Millisecond % 10); 

    return new string(chars); 
} 

private static void Write2Chars(char[] chars, int offset, int value) 
{ 
    chars[offset] = Digit(value/10); 
    chars[offset+1] = Digit(value % 10); 
} 

private static char Digit(int value) 
{ 
    return (char) (value + '0'); 
} 

Ceci est assez laid, mais il est sans doute beaucoup plus efficace ... de référence, bien sûr!

+0

lol; Je pense que nous travaillions sur un code très similaire! –

+1

@Marc grands esprits se ressemblent !! – inspite

+8

... et les imbéciles diffèrent rarement :) –

9

Etes-vous sûr que cela prend 33% du temps? Comment avez-vous mesuré cela? Il semble plus qu'un peu suspect pour moi ...

Cela rend les choses un peu peu plus rapide:

Basic: 2342ms 
Custom: 1319ms 

Ou si on coupe le IO (Stream.Null):

Basic: 2275ms 
Custom: 839ms 

using System.Diagnostics; 
using System; 
using System.IO; 
static class Program 
{ 
    static void Main() 
    { 
     DateTime when = DateTime.Now; 
     const int LOOP = 1000000; 

     Stopwatch basic = Stopwatch.StartNew(); 
     using (TextWriter tw = new StreamWriter("basic.txt")) 
     { 
      for (int i = 0; i < LOOP; i++) 
      { 
       tw.Write(when.ToString("dd.MM.yy HH:mm:ss:fff")); 
      } 
     } 
     basic.Stop(); 
     Console.WriteLine("Basic: " + basic.ElapsedMilliseconds + "ms"); 

     char[] buffer = new char[100]; 
     Stopwatch custom = Stopwatch.StartNew(); 
     using (TextWriter tw = new StreamWriter("custom.txt")) 
     { 
      for (int i = 0; i < LOOP; i++) 
      { 
       WriteDateTime(tw, when, buffer); 
      } 
     } 
     custom.Stop(); 
     Console.WriteLine("Custom: " + custom.ElapsedMilliseconds + "ms"); 
    } 
    static void WriteDateTime(TextWriter output, DateTime when, char[] buffer) 
    { 
     buffer[2] = buffer[5] = '.'; 
     buffer[8] = ' '; 
     buffer[11] = buffer[14] = buffer[17] = ':'; 
     Write2(buffer, when.Day, 0); 
     Write2(buffer, when.Month, 3); 
     Write2(buffer, when.Year % 100, 6); 
     Write2(buffer, when.Hour, 9); 
     Write2(buffer, when.Minute, 12); 
     Write2(buffer, when.Second, 15); 
     Write3(buffer, when.Millisecond, 18); 
     output.Write(buffer, 0, 21); 
    } 
    static void Write2(char[] buffer, int value, int offset) 
    { 
     buffer[offset++] = (char)('0' + (value/10)); 
     buffer[offset] = (char)('0' + (value % 10)); 
    } 
    static void Write3(char[] buffer, int value, int offset) 
    { 
     buffer[offset++] = (char)('0' + (value/100)); 
     buffer[offset++] = (char)('0' + ((value/10) % 10)); 
     buffer[offset] = (char)('0' + (value % 10)); 
    } 
} 
+2

Je ne le crois pas entièrement - quand j'ai fait quelques tests de log j'ai trouvé que les dates et les temps de formatage et d'analyse dominaient l'accès de CPU. Certes, il ne faisait quasiment rien d'autre que la journalisation ... –

+0

Votre microbenchmark est lié à l'E/S, si vous voulez mesurer les cycles CPU, vous devriez probablement laisser tomber l'écriture dans un fichier. –

+1

Oui, mais l'OP écrit dans un fichier, donc c'est un peu artificiel de l'ignorer ... mais en effet, l'utilisation de Stream.Null montrerait plus de différence. –

0

Savez-vous quelle taille chaque enregistrement dans le binaire et le texte les journaux vont être? Si c'est le cas, vous pouvez diviser le traitement du fichier journal sur un certain nombre de threads, ce qui permettrait une meilleure utilisation d'un PC multicœur/processeur. Si cela ne vous dérange pas que le résultat soit dans des fichiers séparés, il serait judicieux d'avoir un disque dur par cœur de cette façon, ce qui réduira la quantité de têtes de disques à déplacer.

1

Ce n'est pas une réponse en soi, mais plutôt un addedum à réponse Jon Skeet execellent, offrant une variante pour le "s" Format (ISO):

/// <summary> 
    ///  Implements a fast method to write a DateTime value to string, in the ISO "s" format. 
    /// </summary> 
    /// <param name="dateTime">The date time.</param> 
    /// <returns></returns> 
    /// <devdoc> 
    ///  This implementation exists just for performance reasons, it is semantically identical to 
    ///  <code> 
    /// text = value.HasValue ? value.Value.ToString("s") : string.Empty; 
    /// </code> 
    ///  However, it runs about 3 times as fast. (Measured using the VS2015 performace profiler) 
    /// </devdoc> 
    public static string ToIsoStringFast(DateTime? dateTime) { 
     if (!dateTime.HasValue) { 
      return string.Empty; 
     } 
     DateTime dt = dateTime.Value; 
     char[] chars = new char[19]; 
     Write4Chars(chars, 0, dt.Year); 
     chars[4] = '-'; 
     Write2Chars(chars, 5, dt.Month); 
     chars[7] = '-'; 
     Write2Chars(chars, 8, dt.Day); 
     chars[10] = 'T'; 
     Write2Chars(chars, 11, dt.Hour); 
     chars[13] = ':'; 
     Write2Chars(chars, 14, dt.Minute); 
     chars[16] = ':'; 
     Write2Chars(chars, 17, dt.Second); 
     return new string(chars); 
    } 

Avec le sérialiseur à 4 chiffres comme :

private static void Write4Chars(char[] chars, int offset, int value) { 
     chars[offset] = Digit(value/1000); 
     chars[offset + 1] = Digit(value/100 % 10); 
     chars[offset + 2] = Digit(value/10 % 10); 
     chars[offset + 3] = Digit(value % 10); 
    } 

Cela tourne environ 3 fois plus vite. (Mesuré avec le profileur de performance VS2015)