Предлагаю вашему вниманию введение в использование 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
