Здесь возможен ряд факторов; во-первых, обратите внимание, что проводной формат буферов протокола использует прямое кодирование UTF-8 для строк; если в ваших данных преобладают строки, в конечном итоге им потребуется примерно столько же места, сколько для TSV.
Буферы протокола также предназначены для хранения структурированных данных, т.е. более сложных моделей, чем сценарий с одной таблицей. Это не сильно влияет на размер , но начните сравнивать с xml / json и т. Д. (Которые больше похожи по возможностям), и разница становится более очевидной.
Кроме того, поскольку буферы протокола довольно плотные (несмотря на UTF-8), в некоторых случаях сжатие может на самом деле увеличить - вы можете проверить, так ли это здесь.
В кратком примере сценария, который вы представляете, оба формата дают примерно одинаковые размеры - большого скачка нет:
protobuf-net, no compression: 2498720 bytes, write 34ms, read 72ms, chk 50000
protobuf-net, gzip: 1521215 bytes, write 234ms, read 146ms, chk 50000
tsv, no compression: 2492591 bytes, write 74ms, read 122ms, chk 50000
tsv, gzip: 1258500 bytes, write 238ms, read 169ms, chk 50000
TSV в этом случае незначительно меньше, но в конечном итоге TSV действительно является очень простым форматом (с очень ограниченными возможностями в отношении структурированных данных), поэтому неудивительно, что он быстрый.
Действительно, если все, что вы храните, это очень простая таблица, TSV - неплохой вариант, однако в конечном итоге это очень ограниченный формат. Я не могу воспроизвести ваш "гораздо больший" пример.
Помимо расширенной поддержки структурированных данных (и других функций), protobuf также уделяет большое внимание производительности обработки. Теперь, поскольку TSV довольно прост, преимущество здесь не будет массивным (но это заметно выше), но опять же: в отличие от xml, json или встроенного BinaryFormatter для теста против форматы со схожими функциями и разница очевидна.
Пример для чисел выше (обновлено для использования BufferedStream):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Text;
using ProtoBuf;
static class Program
{
static void Main()
{
RunTest(12345, 1, new StringWriter()); // let everyone JIT etc
RunTest(12345, 50000, Console.Out); // actual test
Console.WriteLine("(done)");
Console.ReadLine();
}
static void RunTest(int seed, int count, TextWriter cout)
{
var data = InventData(seed, count);
byte[] raw;
Catalog catalog;
var write = Stopwatch.StartNew();
using(var ms = new MemoryStream())
{
Serializer.Serialize(ms, data);
raw = ms.ToArray();
}
write.Stop();
var read = Stopwatch.StartNew();
using(var ms = new MemoryStream(raw))
{
catalog = Serializer.Deserialize<Catalog>(ms);
}
read.Stop();
cout.WriteLine("protobuf-net, no compression: {0} bytes, write {1}ms, read {2}ms, chk {3}", raw.Length, write.ElapsedMilliseconds, read.ElapsedMilliseconds, catalog.Products.Count);
raw = null; catalog = null;
write = Stopwatch.StartNew();
using (var ms = new MemoryStream())
{
using (var gzip = new GZipStream(ms, CompressionMode.Compress, true))
using (var bs = new BufferedStream(gzip, 64 * 1024))
{
Serializer.Serialize(bs, data);
} // need to close gzip to flush it (flush doesn't flush)
raw = ms.ToArray();
}
write.Stop();
read = Stopwatch.StartNew();
using(var ms = new MemoryStream(raw))
using(var gzip = new GZipStream(ms, CompressionMode.Decompress, true))
{
catalog = Serializer.Deserialize<Catalog>(gzip);
}
read.Stop();
cout.WriteLine("protobuf-net, gzip: {0} bytes, write {1}ms, read {2}ms, chk {3}", raw.Length, write.ElapsedMilliseconds, read.ElapsedMilliseconds, catalog.Products.Count);
raw = null; catalog = null;
write = Stopwatch.StartNew();
using (var ms = new MemoryStream())
{
using (var writer = new StreamWriter(ms))
{
WriteTsv(data, writer);
}
raw = ms.ToArray();
}
write.Stop();
read = Stopwatch.StartNew();
using (var ms = new MemoryStream(raw))
using (var reader = new StreamReader(ms))
{
catalog = ReadTsv(reader);
}
read.Stop();
cout.WriteLine("tsv, no compression: {0} bytes, write {1}ms, read {2}ms, chk {3}", raw.Length, write.ElapsedMilliseconds, read.ElapsedMilliseconds, catalog.Products.Count);
raw = null; catalog = null;
write = Stopwatch.StartNew();
using (var ms = new MemoryStream())
{
using (var gzip = new GZipStream(ms, CompressionMode.Compress))
using(var bs = new BufferedStream(gzip, 64 * 1024))
using(var writer = new StreamWriter(bs))
{
WriteTsv(data, writer);
}
raw = ms.ToArray();
}
write.Stop();
read = Stopwatch.StartNew();
using(var ms = new MemoryStream(raw))
using(var gzip = new GZipStream(ms, CompressionMode.Decompress, true))
using(var reader = new StreamReader(gzip))
{
catalog = ReadTsv(reader);
}
read.Stop();
cout.WriteLine("tsv, gzip: {0} bytes, write {1}ms, read {2}ms, chk {3}", raw.Length, write.ElapsedMilliseconds, read.ElapsedMilliseconds, catalog.Products.Count);
}
private static Catalog ReadTsv(StreamReader reader)
{
string line;
List<Product> list = new List<Product>();
while((line = reader.ReadLine()) != null)
{
string[] parts = line.Split('\t');
var row = new Product();
row.Id = int.Parse(parts[0]);
row.Name = parts[1];
row.QuantityAvailable = int.Parse(parts[2]);
row.Price = decimal.Parse(parts[3]);
row.Weight = int.Parse(parts[4]);
row.Sku = parts[5];
list.Add(row);
}
return new Catalog {Products = list};
}
private static void WriteTsv(Catalog catalog, StreamWriter writer)
{
foreach (var row in catalog.Products)
{
writer.Write(row.Id);
writer.Write('\t');
writer.Write(row.Name);
writer.Write('\t');
writer.Write(row.QuantityAvailable);
writer.Write('\t');
writer.Write(row.Price);
writer.Write('\t');
writer.Write(row.Weight);
writer.Write('\t');
writer.Write(row.Sku);
writer.WriteLine();
}
}
static Catalog InventData(int seed, int count)
{
string[] lipsum =
@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
.Split(' ');
char[] skuChars = "0123456789abcdef".ToCharArray();
Random rand = new Random(seed);
var list = new List<Product>(count);
int id = 0;
for (int i = 0; i < count; i++)
{
var row = new Product();
row.Id = id++;
var name = new StringBuilder(lipsum[rand.Next(lipsum.Length)]);
int wordCount = rand.Next(0,5);
for (int j = 0; j < wordCount; j++)
{
name.Append(' ').Append(lipsum[rand.Next(lipsum.Length)]);
}
row.Name = name.ToString();
row.QuantityAvailable = rand.Next(1000);
row.Price = rand.Next(10000)/100M;
row.Weight = rand.Next(100);
char[] sku = new char[10];
for(int j = 0 ; j < sku.Length ; j++)
sku[j] = skuChars[rand.Next(skuChars.Length)];
row.Sku = new string(sku);
list.Add(row);
}
return new Catalog {Products = list};
}
}
[ProtoContract]
public class Catalog
{
[ProtoMember(1, DataFormat = DataFormat.Group)]
public List<Product> Products { get; set; }
}
[ProtoContract]
public class Product
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public int QuantityAvailable { get; set;}
[ProtoMember(4)]
public decimal Price { get; set; }
[ProtoMember(5)]
public int Weight { get; set; }
[ProtoMember(6)]
public string Sku { get; set; }
}