c#最快的序列化机制是什么?

时间:2011-02-05 10:37:48

标签: c# .net serialization

这适用于小型有效载荷。

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

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

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

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

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

9 个答案:

答案 0 :(得分:11)

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

答案 1 :(得分:6)

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

我怀疑你无论你做什么都会在100ms内完成10亿个项目的序列化......我认为这只是一个不合理的期望,特别是如果这是写入磁盘的话。 (显然如果你只是简单地重复覆盖内存的相同位,你会获得比串行化到磁盘更好的性能,但我怀疑这是你真正要做的事情。)

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

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

我现在正在运行我的Protocol Buffers基准测试(它们每秒都会对字节序列化)但我非常怀疑他们会给你你想要的东西。

答案 2 :(得分:4)

你声称小项目比BinaryFormatter慢,但每次测量它我都发现了相反的结果,例如:

Performance Tests of Serializations used by WCF Bindings

我总结一下,特别是对于v2代码,这可能是你最快的选择。如果你可以发布你的特定基准测试场景,我很乐意帮助看看什么是“up”...如果你不能在这里发布,如果你想直接通过电子邮件发送给我(参见个人资料)也可以。我不知道在任何方案下你的陈述时间是否可行,但我非常确定我能比你看到的任何东西都快得多。

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

答案 3 :(得分:2)

序列化对象的唯一原因是使它们与通用传输介质兼容。网络,磁盘等串行器的性能永远不重要,因为传输介质总是比CPU核心的原始性能慢得多。容易两个数量级或更多。

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

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

目前的硬件功能无法实现目标。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

我想看看以下内容。

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

  2. 您正在使用TCP,其开销足够,而且没有有效负载。您应该通过将邮件批量处理到更大的捆绑包和/或查看UDP来最小化这一点。与#1结合使用时进行批量处理可能会让您在平均总体交流时更接近您的要求。

  3. 是否需要双倍的完整数据宽度或是为了方便起见?如果未使用额外位,则在转换为二进制流时,这将是一个优化的机会。

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

答案 7 :(得分:0)

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

这是我的机器上的性能(i7 920,12gb ram)释放模式,没有连接调试器。它在测试期间使用100%cpu,因此该测试受CPU限制。

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;
}

答案 8 :(得分:0)

如果您不想花时间实施全面的显式序列化/反序列化机制,请尝试以下操作:http://james.newtonking.com/json/help/html/JsonNetVsDotNetSerializers.htm ...

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