Serialización ultra-rápida con Protobuf-net

Cuando no nos queda más remedio que preocuparnos por la serialización para aumentar la eficiencia de nuestra aplicación, ya sea reduciendo el tamaño de los datos serializados o disminuyendo el tiempo empleado en la serialización, nuestra mejor baza a día de hoy es utilizar Protocol Buffers:

Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data – think XML, but smaller, faster, and simpler.

Protocol buffers son un mecanismo flexible, eficiente y automatizado para serializar datos estructurados, algo como XML pero más pequeño, más rápido y más simple.

En .NET hay varias librerías para usar Protocol Buffers. Las más utilizadas son el port de la versión java realizado por Jon Skeet y la versión de Mark Gravell, protobuf-net. De estas dos, me quedo con la protobuf-net por su sencillez de uso, su eficiencia y su compatibilidad con casi todas las versiones de .NET (de 2.0 en adelante, incluyendo Mono, Compact Framework y Silverlight).

¿Cómo de eficiente es?

Para ver realmente de qué estamos hablando, es interesante comparar la eficiencia de este protocolo de serialización con otros más conocidos. En la página de protobuf-net encontramos la siguiente comparativa serializando datos de Northwind:

Comparativa de tiempos de serialización

Comparativa de algoritmos de serialización

La mejora de rendimiento, tanto en tiempo como en tamaño, es muy elevada. Con respecto a DataContractSerializer, podemos llegar a conseguir:

  • Reducir en un 60% el tiempo al serializar.
  • Reducir en un 80% el tiempo al deserializar.
  • Reducir en un 80% el tamaño del resultado de la serialización.

Obviamente estos resultados dependen de la naturaleza exacta de los datos que estemos manejando, pero en general las mejoras se mantienen en rangos parecidos a esos.

¿Cómo se usa?

Lo primero que necesitamos es descargar la última versión y añadir en nuestro proyecto una referencia al assembly protobuf-net.dll que necesitemos (en el paquete de descarga se incluyen versiones para distintas versiones de .NET).

Aunque protobuf-net soporta el uso de ficheros .proto para definir los mensajes, existe una forma mucho más sencilla: usar los atributos ProtoContract/ProtoMember para decorar las clases que queremos serializar:

[ProtoContract]
public class Person
{
    [ProtoMember(1)]
    public int Id { get; set; }
    [ProtoMember(2)]
    public string Name { get; set; }
    [ProtoMember(3)]
    public Address Address { get; set; }
    [ProtoMember(4)]
    public Person[] Friends { get; set; }
}

Lo único que hay que tener en cuenta es que cada propiedad decorada con ProtoMember debe estar identificada por un número diferente (tag, en terminología de protocol buffers). Como se ve en el ejemplo, es posible tener jerarquías de objetos complejas o incluir colecciones.

Además de esta opción, protobuf-net permite usar los atributos DataContract/DataMember, evitando así tener que referenciar protobuf-net desde el assembly donde se almacenan las clases a serializar. En ese caso, podemos dejar que sea protobuf-net el que asigne directamente los tags, o asignarlos nosotros manualmente usado la propiedad Order del atributo DataMember:

[DataContract]
public class Person
{
    [DataMember(Order = 1)]
    public int Id { get; set; }
    [DataMember(Order = 2)]
    public string Name { get; set; }
    [DataMember(Order = 3)]
    public Address Address { get; set; }
    [DataMember(Order = 4)]
    public Person[] Friends { get; set; }
}

Una vez que tenemos marcadas nuestras clases con unos atributos u otros, serializarlas es tan sencillo como usar un par de métodos estáticos de la clase Serializer:

// Serializar
var outputStream = GetOutputStream();
Serializer.Serialize(outputStream, person);

// Deserializar
var inputStream = GetInputStream();
var person = Serializer.Deserialize<Person>(inputStream);

Y no hay mucho más. Existen un par de sobrecargas para serializar/deserializar usando un prefijo que indica el tamaño de los datos que, aunque penalizan algo el rendimiento (sobre todo al serializar), permiten usar de forma más segura el protocolo a través de una conexión de red.

¿Por qué es tan eficiente?

Protocol Buffers es un protocolo muy eficiente por varios motivos. El primero de ellos es que se trata de un protocolo binario, en lugar de estar basado en texto como XML o Json. Esto supone directamente un menor consumo de memoria y un menor número de operaciones para convertir desde/hacia texto valores numéricos, fechas, etc.

Además, el resultado de serializar algo con protocol buffer no incluye meta información sobre lo serializado. Por ejempo, para almacenar un campo llamado «age» de tipo entero en XML o Json, tendríamos:

En XML:
    <age>15<age>
En Json:
    { "age" : "15" }

Sin embargo, en protocol buffers únicamente tendríamos el valor binario del tag y el valor binario del campo. Esto hace que nos ahorremos repetir continuamente los nombres de los campos serializados, pero a cambio nos impide interpretar un mensaje si no conocemos cómo está definido y qué tag se corresponde con qué campo.

El principal problema que podemos encontrar al trabajar con protocol buffers es precisamente ese, y es que los mensajes están diseñados para ser eficientes, no para ser legibles, por lo que no será tan sencillo depurar lo que está ocurriendo a partir del contenido de los datos generados.

En cualquier caso, si necesitas un protocolo de serialización rápido y fácil de manejar, pruébalo. Seguramente te sorprenda.

Un comentario en “Serialización ultra-rápida con Protobuf-net

  1. José Hernandez dijo:

    Muy pero que muy interesante, aquí andan con un tema de transferencia de datos y usan el xmlserializer. Probaré este que comentas!

Comentarios cerrados.