2011-11-03 22 views
1

我々は最近、ProtoBuf.NETまたはTSV(タブ区切りのデータ)でシリアル化された同じ表形式のデータ(単一のテーブル、カラム数の半分、製品カタログを記述する)をGZipその後(デフォルトの.NET実装)。ProtoBuf.NETのGZipがタブ区切りファイルのGZipよりも大きいのはなぜですか?

私は、圧縮されたProtoBuf.NETバージョンがテキストバージョンよりも多くのスペースを取ることに気付いています(最大3倍)。 私のペット理論は、ProtoBufが意味論を尊重せず、その結果、GZip周波数圧縮ツリーが一致しないということです。比較的非効率的な圧縮である。

また、ProtoBufは実際には(例えばスキーマのバージョン管理を容易にするために)さらに多くのデータをエンコードするため、シリアライズされたフォーマットは厳密には情報に匹敵するものではありません。

誰も同じ問題を遵守していますか? ProtoBufを圧縮することも価値がありますか?

+1

再「セマンティックバイトを尊重していない」 - あなたはそこに何を意味するか明確にすることができますか? protobufのワイヤフォーマットは、それがあなたが意味するものなら何もサブバイトを行いません - したがって、整列の問題はないはずです。私はここにサンプルを見ることに興味があるので、私は正確な返答をすることができます...(そうでなければ私はちょっとあなたのデータを推測しています) –

+0

申し訳ありませんが、私の誤解は、 –

答えて

5

ここではさまざまな要因が考えられます。第1に、プロトコルバッファーのワイヤー形式は文字列に対して直系の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 = "abcdef".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; } 
} 
3

GZipはストリームコンプレッサです。データを適切にバッファリングしない場合は、小さなブロックでしか動作しないため圧縮効率が非常に悪くなります。

シリアライザとGZipStreamの間に適切なサイズのバッファでBufferedStreamを配置してみてください。

例:GZipStreamに直接書き込むBinaryWriterを使用してInt32シーケンスを1..100'000に圧縮すると、〜650kbになりますが、64kbのBufferedStreamを使用すると圧縮データは約340kbになります。

+0

興味深い...私の例に追加します.... –

+0

heh;私の例にBufferStreamを追加したところ、違いは全くありません; p –

+0

興味深いですが、内部書き込み側(これはとにかく意味があります)に正しくバッファリングしていると思います。 4MBの完全ランダムデータでさえ、1〜2kbのバッファはほぼ正確に入力サイズになりますが、4bブロックでストリームに書き込むと50%大きな結果が得られます。 –