Protocol buffers 为什么ProtoBuf在第一次调用时速度很慢,但在循环内部却非常快?

Protocol buffers 为什么ProtoBuf在第一次调用时速度很慢,但在循环内部却非常快?,protocol-buffers,protobuf-net,Protocol Buffers,Protobuf Net,灵感来源于。我创建了一个小的基准程序来比较ProtoBuf、BinaryFormatter和Json.NET。基准测试本身是一个基于控制台的小型测试。请随意添加/改进,在混合中添加一个新的序列化程序非常简单。无论如何,我的结果是: Binary Formatter ProtoBuf Json.NET ServiceStackJson ServiceStackJSV Loop Size:512 bytes Size:99

灵感来源于。我创建了一个小的基准程序来比较ProtoBuf、BinaryFormatter和Json.NET。基准测试本身是一个基于控制台的小型测试。请随意添加/改进,在混合中添加一个新的序列化程序非常简单。无论如何,我的结果是:

        Binary Formatter         ProtoBuf          Json.NET     ServiceStackJson   ServiceStackJSV
 Loop     Size:512 bytes    Size:99 bytes    Size:205 bytes      Size:205 bytes     Size:181 bytes
    1         16.1242 ms      151.6354 ms       277.2085 ms         129.8321 ms        146.3547 ms
    2          0.0673 ms        0.0349 ms         0.0727 ms           0.0343 ms          0.0370 ms
    4          0.0292 ms        0.0085 ms         0.0303 ms           0.0145 ms          0.0148 ms
    8          0.0255 ms        0.0069 ms         0.0017 ms           0.0216 ms          0.0129 ms
   16          0.0011 ms        0.0064 ms         0.0282 ms           0.0114 ms          0.0120 ms
   32          0.0164 ms        0.0061 ms         0.0334 ms           0.0112 ms          0.0120 ms
   64          0.0347 ms        0.0073 ms         0.0296 ms           0.0121 ms          0.0013 ms
  128          0.0312 ms        0.0058 ms         0.0266 ms           0.0062 ms          0.0117 ms
  256          0.0256 ms        0.0097 ms         0.0448 ms           0.0087 ms          0.0116 ms
  512          0.0261 ms        0.0058 ms         0.0307 ms           0.0127 ms          0.0116 ms
 1024          0.0258 ms        0.0057 ms         0.0309 ms           0.0113 ms          0.0122 ms
 2048          0.0257 ms        0.0059 ms         0.0297 ms           0.0125 ms          0.0121 ms
 4096          0.0247 ms        0.0060 ms         0.0290 ms           0.0119 ms          0.0120 ms
 8192          0.0247 ms        0.0060 ms         0.0286 ms           0.0115 ms          0.0121 ms
免责声明:

  • 上面的结果来自Windows虚拟机-与裸机操作系统相比,非常小间隔的秒表/计时器值可能不是100%准确。因此,忽略上表中的超低值

  • 对于ServiceStack,Json和JSV评分来自两次单独的运行。由于它们共享相同的底层ServiceStack库,因此一个接一个地运行会影响下一次运行的“冷启动”1循环分数(这是“热启动”快)

  • BinaryFormatter是最大的,但也是单个序列化=>反序列化循环中最快的。然而,一旦我们在serialization=>deserialization代码上进行了严格的循环,ProtoBuf就非常快了

    问题#1:为什么ProtoBuf对于单个序列化=>反序列化循环要慢得多

    问题2:从实际角度看,我们能做些什么来克服“冷启动”?是否至少运行一个对象(任何类型)?运行每种(关键)对象类型

    问题#1:为什么ProtoBuf对于单个序列化=>反序列化循环要慢得多

    因为它需要做大量的工作来分析模型和准备策略;我花了很多时间使生成的策略尽可能快得离谱,但可能是我忽略了元编程层的优化。我很高兴把它作为一个项目来考虑,以减少第一次通过的时间。当然,另一方面,元编程层的速度仍然是Json.NET同等预处理速度的两倍;p

    问题#2:从实际角度看,我们能做些什么来克服“冷启动”?至少运行一个对象(任何时间)?运行每种(关键)对象类型

    各种选择:

  • 使用“预编译”工具作为构建过程的一部分,将编译后的序列化程序生成为一个单独的完全静态编译的dll,您可以像正常情况一样引用和使用它:然后发生零元编程
  • 在启动时显式地告诉模型有关“根”类型的信息,并存储
    Compile()的输出

    (Compile()
    方法将从根类型进行分析,同时添加所需的任何其他类型,并返回已编译生成的实例)

  • 在启动时显式地告诉模型有关“根”类型的信息,并调用
    CompileInPlace()
    “几次”
    CompileInPlace()
    不会完全扩展模型,但多次调用它应该可以覆盖大多数基础,因为编译一个层会将其他类型带入模型中

    RuntimeTypeModel.Default.Add(typeof(Foo), true);
    RuntimeTypeModel.Default.Add(typeof(Bar), true);
    for(int i = 0 ; i < 5 ; i++) {
        RuntimeTypeModel.Default.CompileInPlace();
    }
    
    RuntimeTypeModel.Default.Add(typeof(Foo),true);
    RuntimeTypeModel.Default.Add(typeof(Bar),true);
    对于(int i=0;i<5;i++){
    RuntimeTypeModel.Default.CompileInPlace();
    }
    
  • 另外,我可能应该:

  • CompileInPlace
    场景添加一个方法以完全扩展模型
  • 花些时间优化元编程层

  • 最后想一想:
    Compile
    CompileInPlace
    之间的主要区别在于,如果忘记添加一些类型,会发生什么
    CompileInPlace
    对现有模型有效,因此您以后仍然可以添加新类型(隐式或显式),并且它将“正常工作”<代码>编译更为严格:一旦您通过它生成了一个类型,它是固定的,并且只能处理编译时它可以推断的类型。

    也许您可以尝试预编译您的程序。为此,您需要向二进制文件中添加一个强名称并运行ngen。@jpa“预编译”和“添加强名称”是两个基本无关的主题…@MarcGravel,IIRC如果使用ngen将.NET二进制文件预编译到全局程序集缓存中,则它只能使用强名称。但当然还有其他方法可以达到同样的效果。@jpa啊,对;安根。这在这里没有帮助,因为JIT编译不是问题所在。问题是,该库执行元编程,即它在运行时编写IL来处理它接收到的数据(它事先不知道这些数据)。太棒了,我认为CompileInPlace()最适合大多数应用程序。我认为这是内存中的编译,所以假设没有人对宿主应用程序进程拔掉插头,编译结果的生存期是多少?@Sid是的,这完全在内存中;CompileInPlace使用DynamicMethod工作,每种类型分别工作(每种类型的方法根据需要延迟编译)。太好了,谢谢!我在哪里可以阅读或询问ProtoBuf的线程安全性?”“静态”和“内存中”会引起一些危险信号。我可以在这里问,但如果你想找个更好的地方,可以重新安排路线…@Sid实际上,
    static
    RuntimeTypeModel.Default
    ;核心从
    静态
    移动到v2中的实例,使用
    RuntimeTypeModel.Default
    为预先存在的
    序列化程序。*
    方法提供实现。但是:任何
    TypeModel
    (基本上是序列化程序实例)都是完全线程安全的。它包括作为
    序列化程序。*
    公开的所有方法(它是
    RuntimeTypeModel.Default.*
    的代理)
    RuntimeTypeModel.Default.Add(typeof(Foo), true);
    RuntimeTypeModel.Default.Add(typeof(Bar), true);
    for(int i = 0 ; i < 5 ; i++) {
        RuntimeTypeModel.Default.CompileInPlace();
    }