Предлагаю вашему вниманию введение в использование Protocol Buffers на платформе .Net в формате дискуссии. Я расскажу и покажу что это такое и зачем оно нужно .Net разработчику. Топик требует от читателя начального владения языком C# и системой контроля версий SVN. Так как объем материала превышает среднестатистический объем топиков на хабре, которые не вгоняют хаброюзеров в тоску и не заставляют их скроллить до комментариев, было принято решение разбить его на две части. В первой части мы познакомимся с основами и даже напишем (не)много кода!
Согласно определению на официальной странице, Protocol Buffers (protobuf) – это способ кодирования структурированных данных в эффективном и расширяемом формате, применяемый корпорацией Google почти во всех своих продуктах. Для большинства платформ, включая .Net, такой процесс называется сериализацией.
Да вы правы, Google не занимается написанием специализированных библиотек для .Net разработчиков. Однако существует проект protobuf-net (одна из нескольких реализаций protobuf для платформы), который позволяет использовать protobuf в .Net. Им руководит Marc Gravell — завсегдатай stackoverflow.com и участник множества других отличных проектов. Так что вы всегда можете задать ему вопрос, и он с радостью ответит на него (чем и злоупотреблял автор топика).
Когда речь заходит о сериализации в .Net, все обычно вспоминают о существовании бинарного форматера и xml сериализатора. Следующее, что отмечают разработчики, то, что первый быстрый и имеет высокую степень сжатия, но работает только в пределах .Net платформы; а второй представляет данные в человеко-читабельном формате и служит основой для SOAP, который в свою очередь обеспечивает кросс-платформенность. Фактически утверждение, что вам всегда нужно делать выбор между скоростью и переносимостью, принимается за аксиому! Но protobuf позволяет решить обе проблемы сразу.
Да именно это. Давайте рассмотрим небольшой пример, а заодно узнаем как использовать protobuf-net. Предположим, что у нас есть следующие сущности:
Protobuf-net требует использования специальных атрибутов, что напрямую следует из главной особенности формата – зависимости от порядка следования полей. Напишем тест производительности и степени сжатия:
Результаты:
Как вы видите, мы превзошли бинарную сериализацию не только по скорости, но и также по степени сжатия. Единственный недостаток, что protobuf-net потребовалось больше времени на «холодный старт». Но вы можете решить эту проблему используя следующий вспомогательный код:
Остальные тесты и результаты можно посмотреть здесь.
Понимаете, если существуют реализация для нужной вам платформы, вопрос переносимости в большинстве случаев снимается. А реализациии protobuf существует для более чем 20 языков. Полный список можно увидеть здесь. Отмечу только, что для некоторых языков существует более одной реализации. Так что у вас всегда есть выбор.
UPD: Часть 2
Здравствуйте, а что такое Protocol Buffers?
Согласно определению на официальной странице, Protocol Buffers (protobuf) – это способ кодирования структурированных данных в эффективном и расширяемом формате, применяемый корпорацией Google почти во всех своих продуктах. Для большинства платформ, включая .Net, такой процесс называется сериализацией.
Я пользуюсь сервисами Google, но как protobuf поможет мне в разработке .Net приложений?
Да вы правы, Google не занимается написанием специализированных библиотек для .Net разработчиков. Однако существует проект protobuf-net (одна из нескольких реализаций protobuf для платформы), который позволяет использовать protobuf в .Net. Им руководит Marc Gravell — завсегдатай stackoverflow.com и участник множества других отличных проектов. Так что вы всегда можете задать ему вопрос, и он с радостью ответит на него (чем и злоупотреблял автор топика).
Почему мне стоит использовать эту библиотеку вместо встроенных средств?
Когда речь заходит о сериализации в .Net, все обычно вспоминают о существовании бинарного форматера и xml сериализатора. Следующее, что отмечают разработчики, то, что первый быстрый и имеет высокую степень сжатия, но работает только в пределах .Net платформы; а второй представляет данные в человеко-читабельном формате и служит основой для SOAP, который в свою очередь обеспечивает кросс-платформенность. Фактически утверждение, что вам всегда нужно делать выбор между скоростью и переносимостью, принимается за аксиому! Но protobuf позволяет решить обе проблемы сразу.
Так вы утверждаете, что protobuf не уступает бинарной сериализации и к тому же переносим?
Да именно это. Давайте рассмотрим небольшой пример, а заодно узнаем как использовать protobuf-net. Предположим, что у нас есть следующие сущности:
using System;
using ProtoBuf;
namespace Proto.Sample
{
public enum TaskPriority
{
Low,
Medium,
High
}
[Serializable] // <-- Только для BinaryFormatter
[ProtoContract]
public class Task
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public DateTime CreatedAt { get; set; }
[ProtoMember(3)]
public string CreatedBy { get; set; }
[ProtoMember(4)]
public TaskPriority Priority { get; set; }
[ProtoMember(5)]
public string Content { get; set; }
}
}
* This source code was highlighted with Source Code Highlighter.
Protobuf-net требует использования специальных атрибутов, что напрямую следует из главной особенности формата – зависимости от порядка следования полей. Напишем тест производительности и степени сжатия:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using ProtoBuf;
namespace Proto.Sample
{
internal class Program
{
private static void Main(string[] args)
{
var tasks = new List<Task>
{
new Task
{
Id = 1,
CreatedBy = "Steve Jobs",
CreatedAt = DateTime.Now,
Priority = TaskPriority.High,
Content = "Invent new iPhone"
},
new Task
{
Id = 2,
CreatedBy = "Steve Ballmer",
CreatedAt = DateTime.Now.AddDays(-7),
Priority = TaskPriority.Low,
Content = "Install own Skype"
}
};
Console.WriteLine("The test of binary formatter:");
const string file1 = "tasks1.bin";
TestBinaryFormatter(tasks, file1, 1000);
TestBinaryFormatter(tasks, file1, 2000);
TestBinaryFormatter(tasks, file1, 3000);
TestBinaryFormatter(tasks, file1, 4000);
TestBinaryFormatter(tasks, file1, 5000);
Console.WriteLine("\nThe test of protobuf-net:");
const string file2 = "tasks2.bin";
TestProtoBuf(tasks, file2, 1000);
TestProtoBuf(tasks, file2, 2000);
TestProtoBuf(tasks, file2, 3000);
TestProtoBuf(tasks, file2, 4000);
TestProtoBuf(tasks, file2, 5000);
Console.WriteLine("\nThe comparision of file size:");
Console.WriteLine("The size of {0} is {1} bytes", file1, (new FileInfo(file1)).Length);
Console.WriteLine("The size of {0} is {1} bytes", file2, (new FileInfo(file2)).Length);
Console.ReadKey();
}
private static void TestBinaryFormatter(IList<Task> tasks, string fileName, int iterationCount)
{
var stopwatch = new Stopwatch();
var formatter = new BinaryFormatter();
using (var file = File.Create(fileName))
{
stopwatch.Restart();
for (var i = 0; i < iterationCount; i++)
{
file.Position = 0;
formatter.Serialize(file, tasks);
file.Position = 0;
var restoredTasks = (List<Task>)formatter.Deserialize(file);
}
stopwatch.Stop();
Console.WriteLine("{0} iterations in {1} ms", iterationCount, stopwatch.ElapsedMilliseconds);
}
}
private static void TestProtoBuf(IList<Task> tasks, string fileName, int iterationCount)
{
var stopwatch = new Stopwatch();
using (var file = File.Create(fileName))
{
stopwatch.Restart();
for (var i = 0; i < iterationCount; i++)
{
file.Position = 0;
Serializer.Serialize(file, tasks);
file.Position = 0;
var restoredTasks = Serializer.Deserialize<List<Task>>(file);
}
stopwatch.Stop();
Console.WriteLine("{0} iterations in {1} ms", iterationCount, stopwatch.ElapsedMilliseconds);
}
}
}
}
* This source code was highlighted with Source Code Highlighter.
Результаты:
The test of binary formatter:
1000 iterations in 423 ms
2000 iterations in 381 ms
3000 iterations in 532 ms
4000 iterations in 660 ms
5000 iterations in 814 ms
The test of protobuf-net:
1000 iterations in 1056 ms
2000 iterations in 76 ms
3000 iterations in 129 ms
4000 iterations in 152 ms
5000 iterations in 202 ms
The comparision of file size:
The size of tasks1.bin is 710 bytes
The size of tasks2.bin is 101 bytes
* This source code was highlighted with Source Code Highlighter.
Как вы видите, мы превзошли бинарную сериализацию не только по скорости, но и также по степени сжатия. Единственный недостаток, что protobuf-net потребовалось больше времени на «холодный старт». Но вы можете решить эту проблему используя следующий вспомогательный код:
var model = TypeModel.Create();
model.Add(typeof(Task), true);
var compiledModel = model.Compile(path);
compiledModel.Serialize(file, tasks);
* This source code was highlighted with Source Code Highlighter.
Остальные тесты и результаты можно посмотреть здесь.
Ок. Относительно скорости и сжатия вы меня убедили, но как решается проблема переносимости?
Понимаете, если существуют реализация для нужной вам платформы, вопрос переносимости в большинстве случаев снимается. А реализациии protobuf существует для более чем 20 языков. Полный список можно увидеть здесь. Отмечу только, что для некоторых языков существует более одной реализации. Так что у вас всегда есть выбор.
UPD: Часть 2