Nella nostra applicazione abbiamo alcune strutture di dati che tra le altre cose contengono una lista chunk di byte (attualmente esposta come List<byte[]>
). Abbiamo ridotto il numero di byte perché se permettiamo che gli array di byte siano messi sull'heap di oggetti grandi, nel tempo soffriremo della frammentazione della memoria.Utilizzo della memoria serializzazione di array di byte chunked con Protobuf-net
Abbiamo anche iniziato a utilizzare Protobuf-net per serializzare queste strutture, utilizzando la nostra DLL di serializzazione generata.
Tuttavia, abbiamo notato che Protobuf-net sta creando buffer di memoria molto grandi durante la serializzazione. Osservando il codice sorgente, sembra che forse non è in grado di svuotare il buffer interno fino a quando non è stata scritta l'intera struttura List<byte[]>
, poiché in seguito è necessario scrivere la lunghezza totale nella parte anteriore del buffer.
Questo purtroppo annulla il nostro lavoro con il chunking dei byte in primo luogo, e alla fine ci fornisce OutOfMemoryExceptions a causa della frammentazione della memoria (l'eccezione si verifica nel momento in cui Protobuf-net sta tentando di espandere il buffer a oltre 84k, che ovviamente lo mette sul LOH e il nostro utilizzo complessivo della memoria di processo è piuttosto basso).
Se la mia analisi di come funziona Protobuf-net è corretta, c'è un modo per aggirare questo problema?
Aggiornamento
in base alla risposta di Marc, ecco che cosa ho provato:
[ProtoContract]
[ProtoInclude(1, typeof(A), DataFormat = DataFormat.Group)]
public class ABase
{
}
[ProtoContract]
public class A : ABase
{
[ProtoMember(1, DataFormat = DataFormat.Group)]
public B B
{
get;
set;
}
}
[ProtoContract]
public class B
{
[ProtoMember(1, DataFormat = DataFormat.Group)]
public List<byte[]> Data
{
get;
set;
}
}
Poi per serializzare esso:
var a = new A();
var b = new B();
a.B = b;
b.Data = new List<byte[]>
{
Enumerable.Range(0, 1999).Select(v => (byte)v).ToArray(),
Enumerable.Range(2000, 3999).Select(v => (byte)v).ToArray(),
};
var stream = new MemoryStream();
Serializer.Serialize(stream, a);
Tuttavia, se mi attengo un punto di interruzione in ProtoWriter.WriteBytes()
dove chiama DemandSpace()
verso il basso del metodo e passaggio in DemandSpace()
, posso vedere che il buffer non viene svuotato perché writer.flushLock
equivale a 1
.
Se creo un'altra classe di base per umiliare in questo modo:
[ProtoContract]
[ProtoInclude(1, typeof(ABase), DataFormat = DataFormat.Group)]
public class ABaseBase
{
}
[ProtoContract]
[ProtoInclude(1, typeof(A), DataFormat = DataFormat.Group)]
public class ABase : ABaseBase
{
}
Poi writer.flushLock
uguale 2
in DemandSpace()
.
Immagino che ci sia un passo ovvio che mi è mancato fare con i tipi derivati?
Grazie per la rapida risposta. La tua ipotesi sulla nostra struttura dati era corretta. Avrei ragione nel dire che abbiamo bisogno di cambiare il DataFormat in Group per tutte le proprietà che contengono un riferimento ad A, e così via fino alla radice del grafico dell'oggetto? E questo cambiamento dovrebbe anche essere sugli attributi ProtoInclude rilevanti? –
@James essenzialmente, sì. Hmmm ... Forse dovrei aggiungere un default a livello di modello per questo! –
Ho aggiornato la mia domanda con il mio tentativo di utilizzare DataFormat.Group per risolvere il problema, ma ho ancora problemi a scaricare il buffer. Mi scuso se sono un idiota .. –