c# - recursos - Mejore el rendimiento de la serialización binaria para grandes listas de estructuras
mejorar rendimiento android 2018 (2)
Tengo una estructura que contiene coordenadas 3d en 3 ints. En una prueba, armé una Lista <> de 1 millón de puntos aleatorios y luego utilicé la serialización binaria a un flujo de memoria.
El flujo de memoria está llegando en ~ 21 MB, lo que parece muy ineficiente, ya que 1000000 puntos * 3 coords * 4 bytes deben salir con un mínimo de 11MB
También está tomando ~ 3 segundos en mi plataforma de prueba.
¿Alguna idea para mejorar el rendimiento y / o tamaño?
(No tengo que mantener la interfaz ISerialzable si ayuda, podría escribir directamente en un flujo de memoria)
EDITAR - De las respuestas a continuación, he reunido un enfrentamiento de serialización comparando BinaryFormatter, ''Raw'' BinaryWriter y Protobuf
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using ProtoBuf;
namespace asp_heatmap.test
{
[Serializable()] // For .NET BinaryFormatter
[ProtoContract] // For Protobuf
public class Coordinates : ISerializable
{
[Serializable()]
[ProtoContract]
public struct CoOrd
{
public CoOrd(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
[ProtoMember(1)]
public int x;
[ProtoMember(2)]
public int y;
[ProtoMember(3)]
public int z;
}
internal Coordinates()
{
}
[ProtoMember(1)]
public List<CoOrd> Coords = new List<CoOrd>();
public void SetupTestArray()
{
Random r = new Random();
List<CoOrd> coordinates = new List<CoOrd>();
for (int i = 0; i < 1000000; i++)
{
Coords.Add(new CoOrd(r.Next(), r.Next(), r.Next()));
}
}
#region Using Framework Binary Formatter Serialization
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Coords", this.Coords);
}
internal Coordinates(SerializationInfo info, StreamingContext context)
{
this.Coords = (List<CoOrd>)info.GetValue("Coords", typeof(List<CoOrd>));
}
#endregion
# region ''Raw'' Binary Writer serialization
public MemoryStream RawSerializeToStream()
{
MemoryStream stream = new MemoryStream(Coords.Count * 3 * 4 + 4);
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(Coords.Count);
foreach (CoOrd point in Coords)
{
writer.Write(point.x);
writer.Write(point.y);
writer.Write(point.z);
}
return stream;
}
public Coordinates(MemoryStream stream)
{
using (BinaryReader reader = new BinaryReader(stream))
{
int count = reader.ReadInt32();
Coords = new List<CoOrd>(count);
for (int i = 0; i < count; i++)
{
Coords.Add(new CoOrd(reader.ReadInt32(),reader.ReadInt32(),reader.ReadInt32()));
}
}
}
#endregion
}
[TestClass]
public class SerializationTest
{
[TestMethod]
public void TestBinaryFormatter()
{
Coordinates c = new Coordinates();
c.SetupTestArray();
// Serialize to memory stream
MemoryStream mStream = new MemoryStream();
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Serialize(mStream, c);
Console.WriteLine("Length : {0}", mStream.Length);
// Now Deserialize
mStream.Position = 0;
Coordinates c2 = (Coordinates)bformatter.Deserialize(mStream);
Console.Write(c2.Coords.Count);
mStream.Close();
}
[TestMethod]
public void TestBinaryWriter()
{
Coordinates c = new Coordinates();
c.SetupTestArray();
MemoryStream mStream = c.RawSerializeToStream();
Console.WriteLine("Length : {0}", mStream.Length);
// Now Deserialize
mStream.Position = 0;
Coordinates c2 = new Coordinates(mStream);
Console.Write(c2.Coords.Count);
}
[TestMethod]
public void TestProtoBufV2()
{
Coordinates c = new Coordinates();
c.SetupTestArray();
MemoryStream mStream = new MemoryStream();
ProtoBuf.Serializer.Serialize(mStream,c);
Console.WriteLine("Length : {0}", mStream.Length);
mStream.Position = 0;
Coordinates c2 = ProtoBuf.Serializer.Deserialize<Coordinates>(mStream);
Console.Write(c2.Coords.Count);
}
}
}
Resultados (Nota PB v2.0.0.423 beta)
Serialize | Ser + Deserialize | Size
-----------------------------------------------------------
BinaryFormatter 2.89s | 26.00s !!! | 21.0 MB
ProtoBuf v2 0.52s | 0.83s | 18.7 MB
Raw BinaryWriter 0.27s | 0.36s | 11.4 MB
Obviamente, esto solo se trata de velocidad / tamaño y no tiene en cuenta nada más.
La serialización binaria utilizando BinaryFormatter
incluye información de tipo en los bytes que genera. Esto ocupa espacio adicional. Es útil en los casos en que no sabe qué estructura de datos puede esperar en el otro extremo, por ejemplo.
En su caso, usted sabe qué formato tienen los datos en ambos extremos, y eso no suena como si cambiara. Así que puedes escribir un simple método de codificación y decodificación. Tu clase CoOrd ya no necesita ser serializable también.
Yo usaría System.IO.BinaryReader y System.IO.BinaryWriter , luego pasaría por cada una de sus instancias de CoOrd y leería / escribía los valores de propiedad X, Y, Z en el flujo. Esas clases incluso empaquetarán sus entradas en menos de 11MB, asumiendo que muchos de sus números son más pequeños que 0x7F y 0x7FFF.
Algo como esto:
using (var writer = new BinaryWriter(stream)) {
// write the number of items so we know how many to read out
writer.Write(points.Count);
// write three ints per point
foreach (var point in points) {
writer.Write(point.X);
writer.Write(point.Y);
writer.Write(point.Z);
}
}
Para leer de la corriente:
List<CoOrd> points;
using (var reader = new BinaryReader(stream)) {
var count = reader.ReadInt32();
points = new List<CoOrd>(count);
for (int i = 0; i < count; i++) {
var x = reader.ReadInt32();
var y = reader.ReadInt32();
var z = reader.ReadInt32();
points.Add(new CoOrd(x, y, z));
}
}
Para simplificar el uso de un serializador pre- protobuf-net , recomiendo protobuf-net ; Aquí está protobuf-net v2, con solo agregar algunos atributos:
[DataContract]
public class Coordinates
{
[DataContract]
public struct CoOrd
{
public CoOrd(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
[DataMember(Order = 1)]
int x;
[DataMember(Order = 2)]
int y;
[DataMember(Order = 3)]
int z;
}
[DataMember(Order = 1)]
public List<CoOrd> Coords = new List<CoOrd>();
public void SetupTestArray()
{
Random r = new Random(123456);
List<CoOrd> coordinates = new List<CoOrd>();
for (int i = 0; i < 1000000; i++)
{
Coords.Add(new CoOrd(r.Next(10000), r.Next(10000), r.Next(10000)));
}
}
}
utilizando:
ProtoBuf.Serializer.Serialize(mStream, c);
para serializar. Esto requiere 10.960.823 bytes, pero tenga en cuenta que modifiqué SetupTestArray para limitar el tamaño a 10,000, ya que, de forma predeterminada, utiliza la codificación "varint" en los números enteros, que depende del tamaño. 10k no es importante aquí (de hecho, no comprobé cuáles son los "pasos"). Si prefieres un tamaño fijo (que permitirá cualquier rango):
[ProtoMember(1, DataFormat = DataFormat.FixedSize)]
int x;
[ProtoMember(2, DataFormat = DataFormat.FixedSize)]
int y;
[ProtoMember(3, DataFormat = DataFormat.FixedSize)]
int z;
Lo que lleva 16,998,640 bytes.