65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (13投票s)

2011年1月30日

CPOL

3分钟阅读

viewsIcon

65113

downloadIcon

984

使用 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,您可以动态生成序列化代理。以下是基本步骤

  1. 定义一个自定义属性,您可以使用它来标记您要为其生成序列化代理的类。
  2. 在程序集启动期间,遍历所有具有此自定义属性的类型,并为它们生成序列化代理。
  3. 创建一个具有类似于二进制格式化程序的接口的类,该类在内部将调用委托给 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

ILSerializer/image.jpg

这就是 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。

现在回到使用此命名空间创建我们的序列化代理。 以下是步骤。

编写 IL 二进制序列化器的步骤

  1. 定义我们的动态序列化代理将实现的接口
    public interface IHiPerfSerializationSurrogate
    {
     void Serialize(BinaryWriter writer, object graph);
     object DeSerialize(BinaryReader reader);
    }
  2. 使用 AssemblyBuilder 类,在当前应用程序域内创建一个动态程序集
    AssemblyBuilder myAsmBuilder = Thread.GetDomain().DefineDynamicAssembly(
             new AssemblyName("SomeName"),
             AssemblyBuilderAccess.Run);
  3. 在此程序集中,现在定义一个模块
    ModuleBuilder surrogateModule = 
    	myAsmBuilder.DefineDynamicModule("SurrogateModule");
  4. 在模块中,现在定义您的自定义序列化代理
    TypeBuilder surrogateTypeBuilder = surrogateModule.DefineType( 
                                "MyClass_EventSurrogate", TypeAttributes.Public);
  5. 将此类型设为 IHiPerfSerializationSurrogate 的实现
    surrogateTypeBuilder.AddInterfaceImplementation
    		(typeof(IHiPerfSerializationSurrogate));
  6. 现在在代理中定义 Serialize 方法
    Type[] dpParams = new Type[] { typeof(BinaryWriter), typeof(object) };
    MethodBuilder serializeMethod = surrogateTypeBuilder.DefineMethod(
               "Serialize",
               MethodAttributes.Public | MethodAttributes.Virtual,
               typeof(void),dpParams);
  7. 然后为每个 public 属性发出一个 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
  8. 在代理中定义 DeSerialize 方法
    MethodBuilder deserializeMthd = surrogateTypeBuilder.DefineMethod(
                           "DeSerialize",
                        MethodAttributes.Public | MethodAttributes.Virtual | 
                        MethodAttributes.HideBySig | MethodAttributes.Final | 
                        MethodAttributes.NewSlot,
                        typeof(object),
                        dpParams);
  9. 现在为每个属性发出一个 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
  10. 发出序列化代理
    Type HiPerfSurrogate = surrogateTypeBuilder.CreateType();
  11. 现在我们有了一个高性能的序列化代理,是时候使用它了。 这是方法
    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

© . All rights reserved.