【问题标题】:What's the fastest Serialization mechanism for c#?c#最快的序列化机制是什么?
【发布时间】:2011-02-05 10:37:47
【问题描述】:

这适用于小负载。

我希望每 100 毫秒达到 1,000,000,000。

标准的 BinaryFormatter 非常慢。 DataContractSerializer 比 BinaryFormatter 慢。

协议缓冲区 (http://code.google.com/p/protobuf-net/) 似乎比小对象的 BinaryFormatter 慢!

是否有更多的序列化机制应该关注核心编码或开源项目?

编辑: 我在内存中序列化,然后在异步套接字上通过 tcp 传输有效负载。负载在内存中生成,是带有 ulong 标识符的小型双精度数组(10 到 500 点)。

【问题讨论】:

  • 10,000 个项目在一微秒内?您在哪种硬件上运行?
  • 您是否尝试过并行运行以加快速度?
  • Protobuf 是我所知道的最快的。但是你的要求太疯狂了。在普通硬件上,每个项目 0.1 纳秒只是一个周期的一小部分。
  • 我怀疑即使是数据库也能达到这样的速度
  • 每个阵列至少 22 字节 * 10 亿 * 0.1 秒意味着您需要 220GBps = 1760 Gbps 连接最小值。这相当快。这是 Verizon 计划的 100Gbps 骨干网速度的 16 倍多。目前可用的最快中继线仅为 1.6Tbs,低于您的 MINIMUM 要求。基本上,你没有机会。此外,鉴于数据格式的简单性,您应该编写自定义序列化程序。

标签: c# .net serialization


【解决方案1】:

您的性能要求将可用的序列化程序限制为 0。自定义 BinaryWriter 和 BinaryReader 将是您可以获得的最快速度。

【讨论】:

    【解决方案2】:

    我希望 Protobuf-net 即使对于小物体也会更快......但你可能也想试试我的Protocol Buffer port。我已经有一段时间没有使用 Marc 的端口了 - 我上次进行基准测试时速度更快,但我知道从那时起他已经完全重写了 :)

    我怀疑无论你做什么,你能否在 100 毫秒内实现对十亿个项目的序列化……我认为这只是一个不合理的期望,尤其是在写入磁盘的情况下。 (显然,如果您只是重复地覆盖相同的 memory 位,您将获得比序列化到磁盘更好的性能,但我怀疑这真的是您想要做的。)

    如果您能给我们提供更多背景信息,我们或许可以提供更多帮助。例如,您是否能够将负载分散到多台机器上? (多核序列化到同一个 IO 设备不太可能有帮助,因为如果它正在写入磁盘或网络,我不认为这是一个 CPU 绑定操作。)

    编辑:假设每个对象是 10 个双精度数(每个 8 个字节),带有一个 ulong 标识符(4 个字节)。在最小,每个对象有 84 个字节。所以你试图在 100 毫秒内序列化 8.4GB。我真的不认为这是可以实现的,无论你使用什么。

    我现在正在运行我的 Protocol Buffers 基准测试(它们提供每秒序列化的字节数),但我非常怀疑它们能否满足您的需求。

    【讨论】:

    • 您好,感谢您的回复。我在内存中序列化,而不是在异步套接字上通过 tcp 传输有效负载。我会试试你的 PB 库并恢复。
    • @RogerGreen:您真的希望能够在 100 毫秒内通过网络传输十亿个对象吗?它只是不会发生,即使你可以在内存中做那么多。序列化本身不会成为瓶颈。
    • @RogerGreen 你是什么连接速度 8Gb per/sec
    【解决方案3】:

    您声称小项目比 BinaryFormatter 慢,但每次我测量它时,我发现完全相反,例如:

    Performance Tests of Serializations used by WCF Bindings

    我得出结论,尤其是对于 v2 代码,这很可能是您最快的选择。如果您可以发布您的特定基准场景,我会很乐意帮助您查看“更新”的内容......如果您不能在此处发布,如果您想直接通过电子邮件将其发送给我(请参阅个人资料),那也可以。我不知道在任何方案下您所说的时间是否可行,但我非常确定我可以比您所看到的更快地得到您。

    使用 v2 代码,CompileInPlace 提供最快的结果 - 它允许一些 IL 技巧,如果编译为物理 dll,则无法使用。

    【讨论】:

      【解决方案4】:

      序列化对象的唯一原因是使它们与通用传输介质兼容。网络、磁盘等。序列化程序的性能无关紧要,因为传输介质总是比 CPU 内核的原始性能慢得多。轻松提高两个数量级或更多。

      这也是属性是可接受的权衡的原因。它们也是 I/O 绑定的,它们的初始化数据必须从程序集元数据中读取。这需要第一次读取磁盘。

      因此,如果您要设置性能要求,则需要将 99% 的精力放在传输介质的能力上。 100 毫秒内 10 亿个“有效负载”需要非常强大的硬件。假设有效负载为 16 字节,则您需要在一秒钟内移动 160 GB。这甚至超出了机器内部的内存总线带宽。 DDR RAM 以每秒约 5 GB 的速度移动。 1 Gb 以太网 NIC 以每秒 125 兆字节的速度突发。假设没有搜索,商品硬盘驱动器以每秒 65 兆字节的速度移动。

      对于当前的硬件功能,您的目标是不现实的。

      【讨论】:

      • 当然,不同的序列化器有不同的带宽配置文件,虽然我同意 CPU 不是最大的问题(与带宽相比),但它可能很重要 - 例如,同一个盒子上的 IPC,或者使用序列化作为进程内快照/克隆/纪念品等。
      • 它也可能是像 fusionio 这样疯狂的极端
      【解决方案5】:

      您可以通过在数据结构上实现 ISerailizable 来编写自定义序列化。无论如何,您可能会面临来自硬件本身的一些“障碍”,无法按照这些要求进行序列化。

      【讨论】:

      • 使用 ISerializable 您仍然需要支付 BiraryFormatter 类型的元数据税;这是不必要的开销
      【解决方案6】:

      Proto-Buff 非常快,但也有限制。 => http://code.google.com/p/protobuf-net/wiki/Performance

      【讨论】:

        【解决方案7】:

        根据我的经验,Marc's Protocol Buffers implementation 非常好。我没用过Jon's。但是,您应该尝试使用技术来最小化数据,而不是序列化全部。

        我会看看以下内容。

        1. 如果消息很小,您应该查看您拥有的熵。您可能有可以部分或完全删除重复数据的字段。如果仅在两方之间进行通信,您可能会从两端构建字典中受益。

        2. 您正在使用 TCP,它有足够的开销,而顶部没有有效负载。您应该通过将消息批处理到更大的包和/或查看 UDP 来最小化这种情况。与 #1 结合使用时,批处理本身可能会让您在平均总通信量时更接近您的要求。

        3. 是否需要 double 的完整数据宽度,还是为了方便?如果不使用额外的位,这将是转换为二进制流时优化的机会。

        通常,当您必须通过单个接口处理多条消息或者您不知道完整的实现细节时,通用序列化非常有用。在这种情况下,最好构建自己的序列化方法以将单个消息结构直接转换为字节数组。既然你知道完全实现双方直接转换不会有问题。它还可以确保您可以内联代码并尽可能防止装箱/拆箱。

        【讨论】:

          【解决方案8】:

          这是我所知道的最快的方法。它确实有它的缺点。就像火箭一样,你不希望它在你的车上,但它有它的位置。就像您需要设置结构并在管道的两端具有相同的结构一样。结构体需要固定大小,否则会比这个例子复杂。

          这是我在我的机器上获得的性能(i7 920,12gb ram)发布模式,没有附加调试器。在测试期间它使用 100% cpu,所以这个测试是 CPU bound。

          Finished in 3421ms, Processed 52.15 GB
          For data write rate of 15.25 GB/s
          Round trip passed
          

          .. 和代码...

              class Program
          {
              unsafe
              static void Main(string[] args)
              {
                  int arraySize = 100;
                  int iterations = 10000000;
                  ms[] msa = new ms[arraySize];
                  for (int i = 0; i < arraySize; i++)
                  {
                      msa[i].d1 = i + .1d;
                      msa[i].d2 = i + .2d;
                      msa[i].d3 = i + .3d;
                      msa[i].d4 = i + .4d;
                      msa[i].d5 = i + .5d;
                      msa[i].d6 = i + .6d;
                      msa[i].d7 = i + .7d;
                  }
          
                  int sizeOfms = Marshal.SizeOf(typeof(ms));
                  byte[] bytes = new byte[arraySize * sizeOfms];
          
                  TestPerf(arraySize, iterations, msa, sizeOfms, bytes);
          
                  // lets round trip it.
                  var msa2 = new ms[arraySize]; // Array of structs we want to push the bytes into
                  var handle2 = GCHandle.Alloc(msa2, GCHandleType.Pinned);// get handle to that array
                  Marshal.Copy(bytes, 0, handle2.AddrOfPinnedObject(), bytes.Length);// do the copy
                  handle2.Free();// cleanup the handle
          
                  // assert that we didnt lose any data.
                  var passed = true;
                  for (int i = 0; i < arraySize; i++)
                  {
                      if(msa[i].d1 != msa2[i].d1
                          ||msa[i].d1 != msa2[i].d1
                          ||msa[i].d1 != msa2[i].d1
                          ||msa[i].d1 != msa2[i].d1
                          ||msa[i].d1 != msa2[i].d1
                          ||msa[i].d1 != msa2[i].d1
                          ||msa[i].d1 != msa2[i].d1)
                      {passed = false;
                      break;
                      }
                  }
                  Console.WriteLine("Round trip {0}",passed?"passed":"failed");
              }
          
              unsafe private static void TestPerf(int arraySize, int iterations, ms[] msa, int sizeOfms, byte[] bytes)
              {
                  // start benchmark.
                  var sw = Stopwatch.StartNew();
                  // this cheats a little bit and reuses the same buffer 
                  // for each thread, which would not work IRL
                  var plr = Parallel.For(0, iterations/1000, i => // Just to be nice to the task pool, chunk tasks into 1000s
                      {
                          for (int j = 0; j < 1000; j++)
                          {
                              // get a handle to the struc[] we want to copy from
                              var handle = GCHandle.Alloc(msa, GCHandleType.Pinned);
                              Marshal.Copy(handle.AddrOfPinnedObject(), bytes, 0, bytes.Length);// Copy from it
                              handle.Free();// clean up the handle
                              // Here you would want to write to some buffer or something :)
                          }
                      });
                  // Stop benchmark
                  sw.Stop();
                  var size = arraySize * sizeOfms * (double)iterations / 1024 / 1024 / 1024d; // convert to GB from Bytes
                  Console.WriteLine("Finished in {0}ms, Processed {1:N} GB", sw.ElapsedMilliseconds, size);
                  Console.WriteLine("For data write rate of {0:N} GB/s", size / (sw.ElapsedMilliseconds / 1000d));
              }
          }
          
          [StructLayout(LayoutKind.Explicit, Size= 56, Pack=1)]
          struct ms
          {
              [FieldOffset(0)]
              public double d1;
              [FieldOffset(8)]
              public double d2;
              [FieldOffset(16)]
              public double d3;
              [FieldOffset(24)]
              public double d4;
              [FieldOffset(32)]
              public double d5;
              [FieldOffset(40)]
              public double d6;
              [FieldOffset(48)]
              public double d7;
          }
          

          【讨论】:

            【解决方案9】:

            如果你不想花时间实现一个全面的显式序列化/反序列化机制,试试这个:http://james.newtonking.com/json/help/html/JsonNetVsDotNetSerializers.htm ...

            在我使用大型对象(序列化到磁盘时为 1GB+)时,我发现 NewtonSoft 库生成的文件比使用 BinaryFormatter 时小 4.5 倍,处理时间缩短 6 倍。

            【讨论】:

              猜你喜欢
              • 2010-12-27
              • 2011-04-07
              • 2011-04-23
              • 1970-01-01
              • 1970-01-01
              • 2021-01-06
              • 2013-11-23
              • 2013-03-29
              • 1970-01-01
              相关资源
              最近更新 更多