使用 Microsoft Common Intermediate Language 的高性能二进制序列化器






4.88/5 (13投票s)
使用 Microsoft Common Intermediate Language 的高性能二进制序列化器
基本概念
- 序列化:一种将对象的状态转换为可持久化格式的机制。
- 反序列化:从可持久化格式恢复对象的状态。
- 二进制序列化:将对象的状态转换为二进制流的序列化技术。
问题
Microsoft .NET 在 System.Runtime.Serialization.Formatters.Binary
命名空间中提供了一个二进制序列化器。 这是一个简单的例子,说明了它的工作原理
using System;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApplication1
{
[Serializable]
class TestClass
{
public String Name;
}
class Program
{
static void Main(string[] args)
{
TestClass oT = new TestClass();
oT.Name = "Hello Bin Serializer";
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, oT);
}
}
}
此格式化程序生成的二进制流长度为 173 字节,当转换为字符时看起来像
"\0\0\0\0????\0\0\0\0\0\0\0\f\0\0\0JConsoleApplication1,
Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null\0\0\0ConsoleApplication1.TestClass\0\0\
0Name\0\0\0\0\0\0Hello Bin Serializer\v"
仅仅序列化一个简单的类就需要 173 字节!!
这可能不适用于预算内存较低的高性能应用程序。
解决方案
好消息是,您可以编写一个自定义代理类,该类可以将您的类序列化和反序列化为/从二进制流。
坏消息是,您需要为需要序列化的每个类编写此代理。
这就是 .NET IL 和 System.Reflection.Emit.ILGenerator 出现的地方。 使用此类并熟练掌握 MSIL,您可以动态生成序列化代理。以下是基本步骤
- 定义一个自定义属性,您可以使用它来标记您要为其生成序列化代理的类。
- 在程序集启动期间,遍历所有具有此自定义属性的类型,并为它们生成序列化代理。
- 创建一个具有类似于二进制格式化程序的接口的类,该类在内部将调用委托给 IL 序列化代理。
听起来很容易,但不幸的是,最困难的部分是用 IL 编写序列化代理。 好吧,不要灰心,因为我将向您展示如何编写一个,并且还提供一个参考实现,免费。 你觉得怎么样? 这是一笔好交易,对吧?
好的,我们开始吧。 首先,让我快速讲解一下 IL。
快速 IL 教程
用 C# 编写一个简单的 Hello World 应用程序
class HelloIL
{
public static void Main()
{
System.Console.Writeline("Hello IL");
}
}
编译它,然后在 IL DASM(在 Visual Studio 中,转到工具/ILDasm)中打开应用程序。 双击 Main 节点以查看 IL
这就是 IL 的样子
.method public hidebysig static vod Main() cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello IL"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method HelloIL::Main
乐趣从这里开始。 使用 System.Reflection.Emit 命名空间中的以下类,您可以在任何 .NET 应用程序中在运行时生成 IL。
- AssemblyBuilder
- ConstructorBuilder
- CustomAttributeBuilder
- DynamicILInfo
- DynamicMethod
- EnumBuilder
- EventBuilder
- FieldBuilder
- GenericTypeParameterBuilder
- ILGenerator
- LocalBuilder
- MethodBuilder
- MethodRental
- ModuleBuilder
- OpCodes
- ParameterBuilder
- PropertyBuilder
- SignatureHelper
- TypeBuilder
- UnmanagedMarshal
现在回到使用此命名空间创建我们的序列化代理。 以下是步骤。
编写 IL 二进制序列化器的步骤
- 定义我们的动态序列化代理将实现的接口
public interface IHiPerfSerializationSurrogate { void Serialize(BinaryWriter writer, object graph); object DeSerialize(BinaryReader reader); }
- 使用
AssemblyBuilder
类,在当前应用程序域内创建一个动态程序集AssemblyBuilder myAsmBuilder = Thread.GetDomain().DefineDynamicAssembly( new AssemblyName("SomeName"), AssemblyBuilderAccess.Run);
- 在此程序集中,现在定义一个模块
ModuleBuilder surrogateModule = myAsmBuilder.DefineDynamicModule("SurrogateModule");
- 在模块中,现在定义您的自定义序列化代理
TypeBuilder surrogateTypeBuilder = surrogateModule.DefineType( "MyClass_EventSurrogate", TypeAttributes.Public);
- 将此类型设为
IHiPerfSerializationSurrogate
的实现surrogateTypeBuilder.AddInterfaceImplementation (typeof(IHiPerfSerializationSurrogate));
- 现在在代理中定义
Serialize
方法Type[] dpParams = new Type[] { typeof(BinaryWriter), typeof(object) }; MethodBuilder serializeMethod = surrogateTypeBuilder.DefineMethod( "Serialize", MethodAttributes.Public | MethodAttributes.Virtual, typeof(void),dpParams);
- 然后为每个
pub
lic 属性发出一个 getter 方法ILGenerator serializeIL = serializeMethod.GetILGenerator(); MethodInfo mi = EventType.GetMethod("get_" + pi.Name); MethodInfo brWrite = GetBinaryWriterMethod(pi.PropertyType); serializeIL.Emit(OpCodes.Ldarg_1);//PU binary writer serializeIL.Emit(OpCodes.Ldloc, tpmEvent);//PU load the event object serializeIL.EmitCall(OpCodes.Callvirt, mi, null);//PU get val of property serializeIL.EmitCall(OpCodes.Callvirt, brWrite, null);//PU
- 在代理中定义
DeSerializ
e 方法MethodBuilder deserializeMthd = surrogateTypeBuilder.DefineMethod( "DeSerialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.NewSlot, typeof(object), dpParams);
- 现在为每个属性发出一个 setter 方法
ILGenerator deserializeIL = deserializeMthd.GetILGenerator(); MethodInfo setProp = EventType.GetMethod("set_" + pi.Name); deserializeIL.Emit(OpCodes.Ldloc, tpmRetEvent);//load new obj on ES deserializeIL.Emit(OpCodes.Ldarg_1);//PU binary reader ,load BR on ES deserializeIL.EmitCall(OpCodes.Callvirt, brRead, null);//PU deserializeIL.EmitCall(OpCodes.Callvirt, setProp, null);//PU
- 发出序列化代理
Type HiPerfSurrogate = surrogateTypeBuilder.CreateType();
- 现在我们有了一个高性能的序列化代理,是时候使用它了。 这是方法
IHiPerfSerializationSurrogate surrogate =Activator.CreateInstance(HiPerfSurrogate); BinaryWriter binaryWriter = new BinaryWriter(serializationStream); binaryWriter.Write(eventType.FullName); surrogate.Serialize(_binaryWriter, obj);
结果
using System;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApplication1
{
[Serializable]
[ILSerialization.HiPerfSerializable]
public class TestClass
{
public String Name;
}
class Program
{
static void Main(string[] args)
{
int len = int.Parse(args[0]);
TestClass oT = new TestClass();
oT.Name = "Hello Bin Serializer";
System.Diagnostics.Stopwatch w = System.Diagnostics.Stopwatch.StartNew();
w.Start();
for (int i = 0; i < len; i++)
{
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, oT);
ms.Close();
}
w.Stop();
Console.WriteLine("Time elapsed .net binary serializer= "
+ w.ElapsedMilliseconds);
//now let us see how our high performance serializer performs
w = System.Diagnostics.Stopwatch.StartNew();
w.Start();
for (int i = 0; i < len; i++)
{
MemoryStream ms = new MemoryStream();
ILSerialization.Formatters.HiPerfBinaryFormatter hpSer =
new ILSerialization.Formatters.HiPerfBinaryFormatter();
hpSer.Serialize(ms, oT);
ms.Close();
}
w.Stop();
Console.WriteLine("Time elapsed IL hi perf serializer= "
+ w.ElapsedMilliseconds);
}
}
}
序列化在问题部分定义的 TestClass
会产生以下结果
- 字节流大小:.NET 二进制序列化器大小的 1/3rd(51 字节)
- 性能:快 5 倍(对于 1000000 次运行,.NET 序列化器花费了 6602 毫秒,我们的高性能序列化器花费了 1261 毫秒)
参考实现
"HiPerf_IL_CustomSerializer" 是高性能二进制序列化器的参考实现,它比 .NET 二进制序列化器快 5 倍,并且序列化 stream
的大小是其 1/3rd。