2012-12-06 7 views
5

Ispirato da this question. Ho creato un piccolo programma di benchmark per confrontare ProtoBuf, BinaryFormatter e Json.NET. Lo stesso benchmark è una piccola console basata su https://github.com/sidshetye/SerializersCompare. Aggiunta gratuita per aggiungere/migliorare, è abbastanza semplice aggiungere un nuovo serializzatore al mix. In ogni caso, i miei risultati sono i seguenti:Perché ProtoBuf è così lento durante la prima chiamata ma molto veloce nei loop interni?

 Binary Formatter   ProtoBuf   Json.NET  ServiceStackJson ServiceStackJSV 
Loop  Size:512 bytes Size:99 bytes Size:205 bytes  Size:205 bytes  Size:181 bytes 
    1   16.1242 ms  151.6354 ms  277.2085 ms   129.8321 ms  146.3547 ms 
    2   0.0673 ms  0.0349 ms   0.0727 ms   0.0343 ms   0.0370 ms 
    4   0.0292 ms  0.0085 ms   0.0303 ms   0.0145 ms   0.0148 ms 
    8   0.0255 ms  0.0069 ms   0.0017 ms   0.0216 ms   0.0129 ms 
    16   0.0011 ms  0.0064 ms   0.0282 ms   0.0114 ms   0.0120 ms 
    32   0.0164 ms  0.0061 ms   0.0334 ms   0.0112 ms   0.0120 ms 
    64   0.0347 ms  0.0073 ms   0.0296 ms   0.0121 ms   0.0013 ms 
    128   0.0312 ms  0.0058 ms   0.0266 ms   0.0062 ms   0.0117 ms 
    256   0.0256 ms  0.0097 ms   0.0448 ms   0.0087 ms   0.0116 ms 
    512   0.0261 ms  0.0058 ms   0.0307 ms   0.0127 ms   0.0116 ms 
1024   0.0258 ms  0.0057 ms   0.0309 ms   0.0113 ms   0.0122 ms 
2048   0.0257 ms  0.0059 ms   0.0297 ms   0.0125 ms   0.0121 ms 
4096   0.0247 ms  0.0060 ms   0.0290 ms   0.0119 ms   0.0120 ms 
8192   0.0247 ms  0.0060 ms   0.0286 ms   0.0115 ms   0.0121 ms 

responsabilità:

  1. I risultati di cui sopra sono all'interno di un Windows VM - i valori/cronometro per gli intervalli molto piccoli potrebbero non essere accurato al 100% rispetto ai sistemi operativi bare metal. Quindi ignora i valori ultra bassi nella tabella sopra.

  2. Per ServiceStack, JSON e JSV sono stati presi da due serie separate. Dal momento che essi condividono la stessa libreria ServiceStack sottostante, in esecuzione uno dopo l'altro colpisce il "avviamento a freddo" punteggi 1 ciclo per la successiva esecuzione (è 'avvio a caldo' veloci)

BinaryFormatter è il più grande in termini di dimensioni, ma anche il più veloce per una singola serializzazione => ciclo di deserializzazione. Tuttavia, una volta eseguito il ciclo di serializzazione => codice di deserializzazione, ProtoBuf è super veloce.

Domanda n. 1: Perché ProtoBuf è molto più lento per una singola serializzazione => ciclo di deserializzazione?

Domanda n. 2: Da una prospettiva pratica, cosa possiamo fare per superare quella "partenza a freddo"? Esegui almeno un oggetto (di qualsiasi tipo) attraverso di esso? Esegui ogni tipo di oggetto (critico) attraverso di esso?

+0

Forse potresti provare a pre-compilare il tuo programma. Per questo, dovrai aggiungere un nome sicuro al binario ed eseguire ngen. – jpa

+0

@jpa "pre-compilazione" e "aggiungi un nome sicuro" sono due soggetti in gran parte non collegati ... –

+0

@MarcGravell, IIRC se si utilizza ngen per precompilare un binario .NET nella cache di assembly globale, funziona solo con un nome forte Ma ovviamente ci sono altri modi per ottenere lo stesso risultato. – jpa

risposta

10

Domanda n. 1: Perché ProtoBuf è molto più lento per una singola serializzazione => ciclo di deserializzazione?

Perché fa una metrica di lavoro per analizzare il modello e preparare la strategia; Ho passato molto tempo a rendere la strategia generata il più follemente veloce possibile, ma potrebbe essere che ho lesinato sulle ottimizzazioni nel livello di meta-programmazione. Sono felice di aggiungere questo come un oggetto da guardare, per ridurre il tempo in un primo passaggio. Naturalmente, lo strato di meta-programmazione è ancora due volte più veloce del pre-processing equivalente di Json.NET p

Domanda n. 2: Da una prospettiva pratica, cosa possiamo fare per superare quella "partenza a freddo"? Esegui almeno un oggetto (di qualsiasi momento) attraverso di esso? Esegui ogni tipo di oggetto (critico) attraverso di esso?

Varie opzioni:

  1. utilizzare lo strumento "precompilare" come parte del processo di generazione, per generare il serializzatore compilato come DLL compilata completamente statico separato che è possibile fare riferimento e utilizzare come normale: esattamente a zero meta-programmazione poi accade
  2. dire esplicitamente il modello sui tipi di "root" all'avvio, e memorizzare l'output di Compile()

    static TypeModel serializer; 
    ... 
    RuntimeTypeModel.Default.Add(typeof(Foo), true); 
    RuntimeTypeModel.Default.Add(typeof(Bar), true); 
    serializer = RuntimeTypeModel.Default.Compile(); 
    

    (il metodo Compile() analizzerà dalla radice-tipi, aggiungendo in qualsiasi tipo aggiuntivi necessari come va, restituendo un generata un'istanza compilata)

  3. dire esplicitamente il modello sui tipi di "root" all'avvio, e la chiamata CompileInPlace() "alcune volte"; CompileInPlace() non si completa ampliare il modello - ma chiamare un paio di volte dovrebbe coprire la maggior parte delle basi, dal momento che la compilazione di uno strato porterà altri tipi nel modello

    RuntimeTypeModel.Default.Add(typeof(Foo), true); 
    RuntimeTypeModel.Default.Add(typeof(Bar), true); 
    for(int i = 0 ; i < 5 ; i++) { 
        RuntimeTypeModel.Default.CompileInPlace(); 
    } 
    

Separatamente, dovrei probabilmente:

  1. aggiungere un metodo per espandere completamente un modello per la CompileInPlace scenario
  2. passare un po 'di tempo ottimizzando il livello di meta-programmazione

Pensiero finale: la differenza principale tra Compile e CompileInPlace qui sarà ciò che accade se hai dimenticato di aggiungere alcuni tipi; CompileInPlace funziona contro il modello esistente, quindi puoi ancora aggiungere nuovi tipi (implicitamente o esplicitamente) in un secondo momento, e "funzionerà"; Compile è più rigido: una volta generato un tipo tramite questo, è fisso e può gestire solo i tipi che potrebbe dedurre al momento della compilazione.

+0

Impressionante, penso che CompileInPlace() sia il migliore per la maggior parte applicazioni. Considero una compilazione in-memory, quindi supponendo che nessuno tocchi la procedura di hosting, qual è la durata dei risultati della compilazione? – DeepSpace101

+0

@Sid sì, è interamente in memoria; CompileInPlace funziona con DynamicMethod, separatamente per tipo (il metodo di ciascun tipo è compilato in modo pigramente come necessario) –

+0

Grande, grazie! Dove posso leggere o chiedere informazioni sulla sicurezza dei thread di ProtoBuf? 'Statico' e 'in memoria' alzano alcune bandiere rosse. Potrei chiedere qui ma puoi reindirizzare se hai in mente una sede migliore ... – DeepSpace101

Problemi correlati