2012-03-30 16 views
11

Ho un enorme ammasso di dati geografici rappresentati nella struttura di un oggetto semplice costituita solo da strutture. Tutti i miei campi sono di tipo valore.Serializzazione veloce/deserializzazione delle strutture

public struct Child 
{ 
    readonly float X; 
    readonly float Y; 
    readonly int myField; 
} 

public struct Parent 
{ 
    readonly int id; 
    readonly int field1; 
    readonly int field2; 
    readonly Child[] children; 
} 

I dati vengono chunked piacevolmente a piccole porzioni di Parent[] -s. Ogni array contiene alcune migliaia di istanze di Parent. Ho troppi dati per tenere tutto in memoria, quindi ho bisogno di scambiare questi pezzi su disco avanti e indietro. (Un file risulterebbe approssimativamente 2-300 KB).

Quale sarebbe il modo più efficiente di serializzare/deserializzare lo Parent[] in un byte[] per il dumpint su disco e la lettura? Per quanto riguarda la velocità, Sono particolarmente interessato alla deserializzazione rapida, la velocità di scrittura non è così importante.

Sarebbe semplice BinarySerializer abbastanza buono? O dovrei hackerare con StructLayout (see accepted answer)? Non sono sicuro che funzioni con il campo array di Parent.children.

AGGIORNAMENTO: Risposta ai commenti - Sì, gli oggetti sono immutabili (codice aggiornato) e infatti il ​​campo children non è un tipo di valore. 300 KB suonano poco ma ho milioni di file come quello, quindi la velocità conta.

+4

_Tutti i miei campi sono di tipo valore_ - Il campo 'figli 'non è un tipo di valore. –

+1

300KB è una piccola quantità, questa quantità è deserializzata/serializzata in 0,1 s senza ottimizzazioni –

+0

Tutti i tuoi dati sono di sola lettura? – usr

risposta

10

BinarySerializer è un serializzatore molto generale. Non funzionerà come un'implementazione personalizzata.

Fortunatamente per il tuo, i tuoi dati sono costituiti solo da strutture. Ciò significa che sarai in grado di correggere una structlayout per Child e copiare semplicemente l'array child usando codice non sicuro da un byte [] che hai letto dal disco.

Per i genitori non è così facile perché è necessario trattare i bambini separatamente. Vi consiglio di usare un codice non sicuro per copiare i campi copiabili da bit dal byte [] letto e deserializzare i bambini separatamente.

Hai considerato di mappare tutti i bambini in memoria utilizzando i file mappati in memoria? È quindi possibile riutilizzare la funzione di cache dei sistemi operativi e non occuparsi affatto della lettura e della scrittura.

Zero-copy-deserializzazione un bambino [] si presenta così:

byte[] bytes = GetFromDisk(); 
fixed (byte* bytePtr = bytes) { 
Child* childPtr = (Child*)bytePtr; 
//now treat the childPtr as an array: 
var x123 = childPtr[123].X; 

//if we need a real array that can be passed around, we need to copy: 
var childArray = new Child[GetLengthOfDeserializedData()]; 
for (i = [0..length]) { 
    childArray[i] = childPtr[i]; 
} 
} 
+0

Ho cercato file di memoria mappati, sono perfetti per la gestione dell'accesso al disco! Potresti scrivere un esempio per il segmento non sicuro? Come posso "trasmettere" un byte [] a un bambino [] in modalità non sicura? Perché come hai detto, ci vorrebbe zero tempo. – user256890

+1

Ho aggiunto un esempio. Se vuoi una copia zero, devi cambiare la tua app per usare puntatori o IO non sicuri usando ReadFile (direttamente letti in un Child esistente []). Ma la mia ipotesi è che il singolo passaggio di copiatura sia davvero nulla. Le CPU sono buone. – usr

10

Se non si va giù per la fantasia scrivere il proprio percorso serializzatore, è possibile utilizzare il protobuf.net serializzatore. Ecco l'uscita da un piccolo programma di test:

Using 3000 parents, each with 5 children 
BinaryFormatter Serialized in: 00:00:00.1250000 
Memory stream 486218 B 
BinaryFormatter Deserialized in: 00:00:00.1718750 

ProfoBuf Serialized in: 00:00:00.1406250 
Memory stream 318247 B 
ProfoBuf Deserialized in: 00:00:00.0312500 

dovrebbe essere abbastanza auto-esplicativo. Questo era solo per una corsa, ma era abbastanza indicativo della velocità che ho visto (3-5x).

Per rendere le tue strutture serializzabili (con protobuf.net), basta aggiungere i seguenti attributi:

[ProtoContract] 
[Serializable] 
public struct Child 
{ 
    [ProtoMember(1)] public float X; 
    [ProtoMember(2)] public float Y; 
    [ProtoMember(3)] public int myField; 
} 

[ProtoContract] 
[Serializable] 
public struct Parent 
{ 
    [ProtoMember(1)] public int id; 
    [ProtoMember(2)] public int field1; 
    [ProtoMember(3)] public int field2; 
    [ProtoMember(4)] public Child[] children; 
} 

UPDATE:

In realtà, la scrittura di un serializzatore personalizzato è abbastanza facile, ecco un implementazione ridotto all'osso:

class CustSerializer 
{ 
    public void Serialize(Stream stream, Parent[] parents, int childCount) 
    { 
     BinaryWriter sw = new BinaryWriter(stream); 
     foreach (var parent in parents) 
     { 
      sw.Write(parent.id); 
      sw.Write(parent.field1); 
      sw.Write(parent.field2); 

      foreach (var child in parent.children) 
      { 
       sw.Write(child.myField); 
       sw.Write(child.X); 
       sw.Write(child.Y); 
      } 
     } 
    } 

    public Parent[] Deserialize(Stream stream, int parentCount, int childCount) 
    { 
     BinaryReader br = new BinaryReader(stream); 
     Parent[] parents = new Parent[parentCount]; 

     for (int i = 0; i < parentCount; i++) 
     { 
      var parent = new Parent(); 
      parent.id = br.ReadInt32(); 
      parent.field1 = br.ReadInt32(); 
      parent.field2 = br.ReadInt32(); 
      parent.children = new Child[childCount]; 

      for (int j = 0; j < childCount; j++) 
      { 
       var child = new Child(); 
       child.myField = br.ReadInt32(); 
       child.X = br.ReadSingle(); 
       child.Y = br.ReadSingle(); 
       parent.children[j] = child; 
      } 

      parents[i] = parent; 
     } 
     return parents; 
    } 
} 

Ed ecco l'uscita quando eseguito in un semplice test di velocità:

Custom Serialized in: 00:00:00 
Memory stream 216000 B 
Custom Deserialized in: 00:00:00.0156250 

Ovviamente, è molto meno flessibile rispetto agli altri approcci, ma se la velocità è davvero così importante è circa 2-3 volte più veloce del metodo protobuf. Produce anche dimensioni di file minime, quindi scrivere su disco dovrebbe essere più veloce.

+1

Protobuf è un ottimo compromesso tra facilità d'uso e prestazioni nella maggior parte dei casi. Se vuoi diventare pazzo, non può ancora battere le prestazioni di una soluzione personalizzata. Soprattutto un bit-blittable che può avere un costo esattamente pari a zero! – usr

Problemi correlati