.NETオブジェクトをシリアル化および逆シリアル化する最速の方法を探しています。ここに私がこれまで持っているものがあります:
public class TD
{
public List<CT> CTs { get; set; }
public List<TE> TEs { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public static string Serialize(List<TD> tData)
{
var serializer = new XmlSerializer(typeof(List<TD>));
TextWriter writer = new StringWriter();
serializer.Serialize(writer, tData);
return writer.ToString();
}
public static List<TD> Deserialize(string tData)
{
var serializer = new XmlSerializer(typeof(List<TD>));
TextReader reader = new StringReader(tData);
return (List<TD>)serializer.Deserialize(reader);
}
}
protobuf-net を使用したモデル(発明されたCT
およびTE
)は次のとおりです(ただし、XmlSerializer
を使用する機能は保持されます。これは特に移行に役立ちます)。このisが.NETで最も高速な(または確かに最も高速な)汎用シリアライザーであることを謙虚に(必要であれば多くの証拠と共に)提出します。
文字列が必要な場合は、base-64でバイナリをエンコードします。
[XmlType]
public class CT {
[XmlElement(Order = 1)]
public int Foo { get; set; }
}
[XmlType]
public class TE {
[XmlElement(Order = 1)]
public int Bar { get; set; }
}
[XmlType]
public class TD {
[XmlElement(Order=1)]
public List<CT> CTs { get; set; }
[XmlElement(Order=2)]
public List<TE> TEs { get; set; }
[XmlElement(Order = 3)]
public string Code { get; set; }
[XmlElement(Order = 4)]
public string Message { get; set; }
[XmlElement(Order = 5)]
public DateTime StartDate { get; set; }
[XmlElement(Order = 6)]
public DateTime EndDate { get; set; }
public static byte[] Serialize(List<TD> tData) {
using (var ms = new MemoryStream()) {
ProtoBuf.Serializer.Serialize(ms, tData);
return ms.ToArray();
}
}
public static List<TD> Deserialize(byte[] tData) {
using (var ms = new MemoryStream(tData)) {
return ProtoBuf.Serializer.Deserialize<List<TD>>(ms);
}
}
}
Protobufは非常に高速です。
このシステムのパフォーマンスと実装に関する詳細情報については、 http://code.google.com/p/protobuf-net/wiki/Performance を参照してください。
超高速であると主張するさらに別のシリアライザーは、 netserializer です。
彼らのサイトで与えられたデータは、protobufの2倍のパフォーマンスを示しています。
これに興味を持って、私はできる限り近い「リンゴからリンゴ」テストで提案された方法をテストすることにしました。次のコードでコンソールアプリを作成しました。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace SerializationTests
{
class Program
{
static void Main(string[] args)
{
var count = 100000;
var rnd = new Random(DateTime.UtcNow.GetHashCode());
Console.WriteLine("Generating {0} arrays of data...", count);
var arrays = new List<int[]>();
for (int i = 0; i < count; i++)
{
var elements = rnd.Next(1, 100);
var array = new int[elements];
for (int j = 0; j < elements; j++)
{
array[j] = rnd.Next();
}
arrays.Add(array);
}
Console.WriteLine("Test data generated.");
var stopWatch = new Stopwatch();
Console.WriteLine("Testing BinarySerializer...");
var binarySerializer = new BinarySerializer();
var binarySerialized = new List<byte[]>();
var binaryDeserialized = new List<int[]>();
stopWatch.Reset();
stopWatch.Start();
foreach (var array in arrays)
{
binarySerialized.Add(binarySerializer.Serialize(array));
}
stopWatch.Stop();
Console.WriteLine("BinaryFormatter: Serializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
stopWatch.Reset();
stopWatch.Start();
foreach (var serialized in binarySerialized)
{
binaryDeserialized.Add(binarySerializer.Deserialize<int[]>(serialized));
}
stopWatch.Stop();
Console.WriteLine("BinaryFormatter: Deserializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
Console.WriteLine();
Console.WriteLine("Testing ProtoBuf serializer...");
var protobufSerializer = new ProtoBufSerializer();
var protobufSerialized = new List<byte[]>();
var protobufDeserialized = new List<int[]>();
stopWatch.Reset();
stopWatch.Start();
foreach (var array in arrays)
{
protobufSerialized.Add(protobufSerializer.Serialize(array));
}
stopWatch.Stop();
Console.WriteLine("ProtoBuf: Serializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
stopWatch.Reset();
stopWatch.Start();
foreach (var serialized in protobufSerialized)
{
protobufDeserialized.Add(protobufSerializer.Deserialize<int[]>(serialized));
}
stopWatch.Stop();
Console.WriteLine("ProtoBuf: Deserializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
Console.WriteLine();
Console.WriteLine("Testing NetSerializer serializer...");
var netSerializerSerializer = new ProtoBufSerializer();
var netSerializerSerialized = new List<byte[]>();
var netSerializerDeserialized = new List<int[]>();
stopWatch.Reset();
stopWatch.Start();
foreach (var array in arrays)
{
netSerializerSerialized.Add(netSerializerSerializer.Serialize(array));
}
stopWatch.Stop();
Console.WriteLine("NetSerializer: Serializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
stopWatch.Reset();
stopWatch.Start();
foreach (var serialized in netSerializerSerialized)
{
netSerializerDeserialized.Add(netSerializerSerializer.Deserialize<int[]>(serialized));
}
stopWatch.Stop();
Console.WriteLine("NetSerializer: Deserializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
Console.WriteLine("Press any key to end.");
Console.ReadKey();
}
public class BinarySerializer
{
private static readonly BinaryFormatter Formatter = new BinaryFormatter();
public byte[] Serialize(object toSerialize)
{
using (var stream = new MemoryStream())
{
Formatter.Serialize(stream, toSerialize);
return stream.ToArray();
}
}
public T Deserialize<T>(byte[] serialized)
{
using (var stream = new MemoryStream(serialized))
{
var result = (T)Formatter.Deserialize(stream);
return result;
}
}
}
public class ProtoBufSerializer
{
public byte[] Serialize(object toSerialize)
{
using (var stream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(stream, toSerialize);
return stream.ToArray();
}
}
public T Deserialize<T>(byte[] serialized)
{
using (var stream = new MemoryStream(serialized))
{
var result = ProtoBuf.Serializer.Deserialize<T>(stream);
return result;
}
}
}
public class NetSerializer
{
private static readonly NetSerializer Serializer = new NetSerializer();
public byte[] Serialize(object toSerialize)
{
return Serializer.Serialize(toSerialize);
}
public T Deserialize<T>(byte[] serialized)
{
return Serializer.Deserialize<T>(serialized);
}
}
}
}
結果は私を驚かせました。複数回実行した場合、それらは一貫していました。
Generating 100000 arrays of data...
Test data generated.
Testing BinarySerializer...
BinaryFormatter: Serializing took 336.8392ms.
BinaryFormatter: Deserializing took 208.7527ms.
Testing ProtoBuf serializer...
ProtoBuf: Serializing took 2284.3827ms.
ProtoBuf: Deserializing took 2201.8072ms.
Testing NetSerializer serializer...
NetSerializer: Serializing took 2139.5424ms.
NetSerializer: Deserializing took 2113.7296ms.
Press any key to end.
これらの結果を収集し、ProtoBufまたはNetSerializerが大きなオブジェクトでより良いパフォーマンスを発揮するかどうかを確認することにしました。コレクションカウントを10,000個のオブジェクトに変更しましたが、配列のサイズを1〜100ではなく1〜10,000に増やしました。結果はさらに明確に見えました:
Generating 10000 arrays of data...
Test data generated.
Testing BinarySerializer...
BinaryFormatter: Serializing took 285.8356ms.
BinaryFormatter: Deserializing took 206.0906ms.
Testing ProtoBuf serializer...
ProtoBuf: Serializing took 10693.3848ms.
ProtoBuf: Deserializing took 5988.5993ms.
Testing NetSerializer serializer...
NetSerializer: Serializing took 9017.5785ms.
NetSerializer: Deserializing took 5978.7203ms.
Press any key to end.
したがって、私の結論は次のとおりです。ProtoBufとNetSerializerが適している場合がありますが、少なくとも比較的単純なオブジェクトの生のパフォーマンスに関しては... BinaryFormatterは、少なくとも1桁ははるかに優れたパフォーマンスを発揮します。
YMMV。
.netに含まれるバイナリシリアライザーは、XmlSerializerよりも高速である必要があります。または、protobuf、jsonなどの別のシリアライザー...
ただし、それらの一部については、属性を追加するか、メタデータを追加する他の方法が必要です。たとえば、ProtoBufは内部で数値のプロパティIDを使用しますが、マッピングは別のメカニズムで何らかの形で保存する必要があります。バージョン管理は、どのシリアライザーでも簡単ではありません。
上記のコードのバグを削除し、以下の結果になりました:また、NetSerializerでシリアル化する型を登録する必要があるかどうか、どのような互換性またはパフォーマンスの違いが生じる可能性があるかについてもわかりません。
Generating 100000 arrays of data...
Test data generated.
Testing BinarySerializer...
BinaryFormatter: Serializing took 508.9773ms.
BinaryFormatter: Deserializing took 371.8499ms.
Testing ProtoBuf serializer...
ProtoBuf: Serializing took 3280.9185ms.
ProtoBuf: Deserializing took 3190.7899ms.
Testing NetSerializer serializer...
NetSerializer: Serializing took 427.1241ms.
NetSerializer: Deserializing took 78.954ms.
Press any key to end.
変更されたコード
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace SerializationTests
{
class Program
{
static void Main(string[] args)
{
var count = 100000;
var rnd = new Random((int)DateTime.UtcNow.Ticks & 0xFF);
Console.WriteLine("Generating {0} arrays of data...", count);
var arrays = new List<int[]>();
for (int i = 0; i < count; i++)
{
var elements = rnd.Next(1, 100);
var array = new int[elements];
for (int j = 0; j < elements; j++)
{
array[j] = rnd.Next();
}
arrays.Add(array);
}
Console.WriteLine("Test data generated.");
var stopWatch = new Stopwatch();
Console.WriteLine("Testing BinarySerializer...");
var binarySerializer = new BinarySerializer();
var binarySerialized = new List<byte[]>();
var binaryDeserialized = new List<int[]>();
stopWatch.Reset();
stopWatch.Start();
foreach (var array in arrays)
{
binarySerialized.Add(binarySerializer.Serialize(array));
}
stopWatch.Stop();
Console.WriteLine("BinaryFormatter: Serializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
stopWatch.Reset();
stopWatch.Start();
foreach (var serialized in binarySerialized)
{
binaryDeserialized.Add(binarySerializer.Deserialize<int[]>(serialized));
}
stopWatch.Stop();
Console.WriteLine("BinaryFormatter: Deserializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
Console.WriteLine();
Console.WriteLine("Testing ProtoBuf serializer...");
var protobufSerializer = new ProtoBufSerializer();
var protobufSerialized = new List<byte[]>();
var protobufDeserialized = new List<int[]>();
stopWatch.Reset();
stopWatch.Start();
foreach (var array in arrays)
{
protobufSerialized.Add(protobufSerializer.Serialize(array));
}
stopWatch.Stop();
Console.WriteLine("ProtoBuf: Serializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
stopWatch.Reset();
stopWatch.Start();
foreach (var serialized in protobufSerialized)
{
protobufDeserialized.Add(protobufSerializer.Deserialize<int[]>(serialized));
}
stopWatch.Stop();
Console.WriteLine("ProtoBuf: Deserializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
Console.WriteLine();
Console.WriteLine("Testing NetSerializer serializer...");
var netSerializerSerialized = new List<byte[]>();
var netSerializerDeserialized = new List<int[]>();
stopWatch.Reset();
stopWatch.Start();
var netSerializerSerializer = new NS();
foreach (var array in arrays)
{
netSerializerSerialized.Add(netSerializerSerializer.Serialize(array));
}
stopWatch.Stop();
Console.WriteLine("NetSerializer: Serializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
stopWatch.Reset();
stopWatch.Start();
foreach (var serialized in netSerializerSerialized)
{
netSerializerDeserialized.Add(netSerializerSerializer.Deserialize<int[]>(serialized));
}
stopWatch.Stop();
Console.WriteLine("NetSerializer: Deserializing took {0}ms.", stopWatch.Elapsed.TotalMilliseconds);
Console.WriteLine("Press any key to end.");
Console.ReadKey();
}
public class BinarySerializer
{
private static readonly BinaryFormatter Formatter = new BinaryFormatter();
public byte[] Serialize(object toSerialize)
{
using (var stream = new MemoryStream())
{
Formatter.Serialize(stream, toSerialize);
return stream.ToArray();
}
}
public T Deserialize<T>(byte[] serialized)
{
using (var stream = new MemoryStream(serialized))
{
var result = (T)Formatter.Deserialize(stream);
return result;
}
}
}
public class ProtoBufSerializer
{
public byte[] Serialize(object toSerialize)
{
using (var stream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(stream, toSerialize);
return stream.ToArray();
}
}
public T Deserialize<T>(byte[] serialized)
{
using (var stream = new MemoryStream(serialized))
{
var result = ProtoBuf.Serializer.Deserialize<T>(stream);
return result;
}
}
}
public class NS
{
NetSerializer.Serializer Serializer = new NetSerializer.Serializer(new Type[] { typeof(int), typeof(int[]) });
public byte[] Serialize(object toSerialize)
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, toSerialize);
return stream.ToArray();
}
}
public T Deserialize<T>(byte[] serialized)
{
using (var stream = new MemoryStream(serialized))
{
Serializer.Deserialize(stream, out var result);
return (T)result;
}
}
}
}
}
私はあなたのクラスを CGbRジェネレーター に自由に取り入れました。 初期段階であるため、 生成されたシリアル化コードは次のようになります。DateTime
はまだサポートされていないため、単純にlongに置き換えました。
public int Size
{
get
{
var size = 24;
// Add size for collections and strings
size += Cts == null ? 0 : Cts.Count * 4;
size += Tes == null ? 0 : Tes.Count * 4;
size += Code == null ? 0 : Code.Length;
size += Message == null ? 0 : Message.Length;
return size;
}
}
public byte[] ToBytes(byte[] bytes, ref int index)
{
if (index + Size > bytes.Length)
throw new ArgumentOutOfRangeException("index", "Object does not fit in array");
// Convert Cts
// Two bytes length information for each dimension
GeneratorByteConverter.Include((ushort)(Cts == null ? 0 : Cts.Count), bytes, ref index);
if (Cts != null)
{
for(var i = 0; i < Cts.Count; i++)
{
var value = Cts[i];
value.ToBytes(bytes, ref index);
}
}
// Convert Tes
// Two bytes length information for each dimension
GeneratorByteConverter.Include((ushort)(Tes == null ? 0 : Tes.Count), bytes, ref index);
if (Tes != null)
{
for(var i = 0; i < Tes.Count; i++)
{
var value = Tes[i];
value.ToBytes(bytes, ref index);
}
}
// Convert Code
GeneratorByteConverter.Include(Code, bytes, ref index);
// Convert Message
GeneratorByteConverter.Include(Message, bytes, ref index);
// Convert StartDate
GeneratorByteConverter.Include(StartDate.ToBinary(), bytes, ref index);
// Convert EndDate
GeneratorByteConverter.Include(EndDate.ToBinary(), bytes, ref index);
return bytes;
}
public Td FromBytes(byte[] bytes, ref int index)
{
// Read Cts
var ctsLength = GeneratorByteConverter.ToUInt16(bytes, ref index);
var tempCts = new List<Ct>(ctsLength);
for (var i = 0; i < ctsLength; i++)
{
var value = new Ct().FromBytes(bytes, ref index);
tempCts.Add(value);
}
Cts = tempCts;
// Read Tes
var tesLength = GeneratorByteConverter.ToUInt16(bytes, ref index);
var tempTes = new List<Te>(tesLength);
for (var i = 0; i < tesLength; i++)
{
var value = new Te().FromBytes(bytes, ref index);
tempTes.Add(value);
}
Tes = tempTes;
// Read Code
Code = GeneratorByteConverter.GetString(bytes, ref index);
// Read Message
Message = GeneratorByteConverter.GetString(bytes, ref index);
// Read StartDate
StartDate = DateTime.FromBinary(GeneratorByteConverter.ToInt64(bytes, ref index));
// Read EndDate
EndDate = DateTime.FromBinary(GeneratorByteConverter.ToInt64(bytes, ref index));
return this;
}
次のようなサンプルオブジェクトのリストを作成しました。
var objects = new List<Td>();
for (int i = 0; i < 1000; i++)
{
var obj = new Td
{
Message = "Hello my friend",
Code = "Some code that can be put here",
StartDate = DateTime.Now.AddDays(-7),
EndDate = DateTime.Now.AddDays(2),
Cts = new List<Ct>(),
Tes = new List<Te>()
};
for (int j = 0; j < 10; j++)
{
obj.Cts.Add(new Ct { Foo = i * j });
obj.Tes.Add(new Te { Bar = i + j });
}
objects.Add(obj);
}
マシンのRelease
ビルドの結果:
var watch = new Stopwatch();
watch.Start();
var bytes = BinarySerializer.SerializeMany(objects);
watch.Stop();
サイズ:149000バイト
時間: 2.059ms 3.13ms
編集:CGbR 0.4.3以降、バイナリシリアライザーはDateTimeをサポートします。残念ながら、DateTime.ToBinary
メソッドはめちゃくちゃ遅いです。私はそれをすぐにsomehtingでそれを取り替えます。
Edit2:ToUniversalTime()
を呼び出してUTC DateTime
を使用すると、パフォーマンスが復元され、1.669ms。
Salar.Bois まともなパフォーマンスのシリアライザーを試すことができます。ペイロードサイズに重点が置かれていますが、優れたパフォーマンスも提供します。
自分で結果を確認して比較したい場合は、Githubページにベンチマークがあります。