【问题标题】:Fastest way of reading and writing binary读写二进制的最快方式
【发布时间】:2011-01-03 10:47:50
【问题描述】:

我目前正在优化一个应用程序,其中一项经常进行的操作是读取和写入二进制文件。我需要两种功能:

Set(byte[] target, int index, int value);

int Get(byte[] source, int index);

这些函数是有符号和无符号短、整数和长字节序的大字节序和小字节序所需要的。

以下是我制作的一些示例,但我需要评估一下优缺点:

第一种方法是使用 Marshal 将值写入 byte[] 的内存中,第二种方法是使用普通指针来完成此操作,第三种方法是使用 BitConverter 和 BlockCopy 来完成此操作

unsafe void Set(byte[] target, int index, int value)
{
    fixed (byte* p = &target[0])
    {
        Marshal.WriteInt32(new IntPtr(p), index, value);
    }
}

unsafe void Set(byte[] target, int index, int value)
{
    int* p = &value;
    for (int i = 0; i < 4; i++)
    {
        target[offset + i] = *((byte*)p + i);
    }
}

void Set(byte[] target, int index, int value)
{
    byte[] data = BitConverter.GetBytes(value);
    Buffer.BlockCopy(data, 0, target, index, data.Length);
}

这里是读取/获取方法:

第一个是使用 Marshal 从 byte[] 读取值,第二个是使用普通指针,第三个是再次使用 BitConverter:

unsafe int Get(byte[] source, int index)
{
    fixed (byte* p = &source[0])
    {
        return Marshal.ReadInt32(new IntPtr(p), index);
    }
}

unsafe int Get(byte[] source, int index)
{
    fixed (byte* p = &source[0])
    {
        return *(int*)(p + index);
    }
}

unsafe int Get(byte[] source, int index)
{
    return BitConverter.ToInt32(source, index);
}

需要进行边界检查,但还不是我的问题的一部分...

如果有人能告诉我在这种情况下什么是最好和最快的方法,或者给我一些其他解决方案,我会很高兴。一个通用的解决方案会更好


我刚刚做了一些性能测试,结果如下:

设置元帅:45 毫秒,设置指针:48 毫秒,设置 BitConverter:71 毫秒 获取 Marshal:45 毫秒,获取指针:26 毫秒,获取 BitConverter:30 毫秒

似乎使用指针是快速的方法,但我认为 Marshal 和 BitConverter 会进行一些内部检查...有人可以验证吗?

【问题讨论】:

  • 你有代码,为什么不运行它并使用Stopwatch 进行测试?
  • :/ :\ 你说得对,我会这样做是为了快速编辑我的问题,但这不是我帖子的唯一要点。我正在寻找替代品,也许还有通用的方法
  • 对这个问题提出了质疑:只有 I/O 才需要转换为二进制文件。 I/O 操作本身总是比处理位慢几个数量级。最好的优化只能为您带来百分之几的改进。

标签: c# file-io binary


【解决方案1】:

重要提示:如果您只需要一个字节序,请参阅 wj32 / dtb 的指针魔法


就我个人而言,我会直接写入Stream(可能带有一些缓冲),并重新使用我通常认为是干净的共享缓冲区。然后你可以做一些捷径,假设索引为 0/1/2/3。

当然不要使用BitConverter,因为它不能用于您需要的小端/大端。我也倾向于只使用位移而不是不安全等。它实际上是最快的,基于以下(所以我很高兴这就是我的代码here,寻找@987654324 @):

Set1: 371ms
Set2: 171ms
Set3: 993ms
Set4: 91ms <==== bit-shifting ;-p

代码:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
static class Program
{
    static void Main()
    {
        const int LOOP = 10000000, INDEX = 100, VALUE = 512;
        byte[] buffer = new byte[1024];
        Stopwatch watch;

        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Set1(buffer, INDEX, VALUE);
        }
        watch.Stop();
        Console.WriteLine("Set1: " + watch.ElapsedMilliseconds + "ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Set2(buffer, INDEX, VALUE);
        }
        watch.Stop();
        Console.WriteLine("Set2: " + watch.ElapsedMilliseconds + "ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Set3(buffer, INDEX, VALUE);
        }
        watch.Stop();
        Console.WriteLine("Set3: " + watch.ElapsedMilliseconds + "ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Set4(buffer, INDEX, VALUE);
        }
        watch.Stop();
        Console.WriteLine("Set4: " + watch.ElapsedMilliseconds + "ms");

        Console.WriteLine("done");
        Console.ReadLine();
    }
    unsafe static void Set1(byte[] target, int index, int value)
    {
        fixed (byte* p = &target[0])
        {
            Marshal.WriteInt32(new IntPtr(p), index, value);
        }
    }

    unsafe static void Set2(byte[] target, int index, int value)
    {
        int* p = &value;
        for (int i = 0; i < 4; i++)
        {
            target[index + i] = *((byte*)p + i);
        }
    }

    static void Set3(byte[] target, int index, int value)
    {
        byte[] data = BitConverter.GetBytes(value);
        Buffer.BlockCopy(data, 0, target, index, data.Length);
    }
    static void Set4(byte[] target, int index, int value)
    {
        target[index++] = (byte)value;
        target[index++] = (byte)(value >> 8);
        target[index++] = (byte)(value >> 16);
        target[index] = (byte)(value >> 24);
    }
}

【讨论】:

  • 我不认为 Stream 会是一个好的解决方案,问题是可能需要搜索,并且数据并不总是按顺序读取和写入。另一个问题是字节序..
  • 我需要先验证这一点,并且可以接受这个答案作为解决方案,那么获取/阅读呢?
  • 相同,但相反; return ((int)buffer[index++]) | (((int)buffer[index++]) &lt;&lt; 8) | (((int)buffer[index++]) &lt;&lt; 16) | (((int)buffer[index]) &lt;&lt; 24);(或从上到下切换班次以获得其他端序)。请注意,我们提前转换为 int,因为 int 算术比字节算术更快。
  • 我认为这个时候 shift 的解决方案是最好的,最大的优势是易于交换字节序。
【解决方案2】:

使用 Marc Gravell 的 Set1Set4 和下面的 Set5,我在我的机器上得到以下数字:

Set1: 197ms
Set2: 102ms
Set3: 604ms
Set4: 68ms
Set5: 55ms <==== pointer magic ;-p

代码:

unsafe static void Set5(byte[] target, int index, int value)
{
    fixed (byte* p = &target[index])
    {
        *((int*)p) = value;                
    }
}

当然,当字节数组不固定在每次迭代中而只固定一次时,它会快得多

Set6: 10ms (little endian)
Set7: 85ms (big endian)

代码:

if (!BitConverter.IsLittleEndian)
{
    throw new NotSupportedException();
}

watch = Stopwatch.StartNew();
fixed (byte* p = buffer)
{
    for (int i = 0; i < LOOP; i++)
    {
        *((int*)(p + INDEX)) = VALUE;
    }
}
watch.Stop();
Console.WriteLine("Set6: " + watch.ElapsedMilliseconds + "ms");

watch = Stopwatch.StartNew();
fixed (byte* p = buffer)
{
    for (int i = 0; i < LOOP; i++)
    {
        *((int*)(p + INDEX)) = System.Net.IPAddress.HostToNetworkOrder(VALUE);
    }
}
watch.Stop();
Console.WriteLine("Set7: " + watch.ElapsedMilliseconds + "ms");

【讨论】:

  • 这里的问题是字节序交换产生的开销
  • 酷;我已经添加了一个更新,但字节序在这里仍然是一个问题。我今天已经没有选票了,但是虚拟 +1
  • @haze4real:字节序交换产生的开销实际上并没有那么大。示例已更新。
  • :/ 开销很大,在您的测试中慢了大约 8 倍。您不应该在测试中将字节数组固定在循环之外,因为它是需要测试的函数......我无法在这些函数调用之间固定它
  • 对,如果要连续多次修改同一个数组,则只能将数组固定在循环之外。如果您的应用程序不是这种情况,则不能这样做。但如果可以的话,性能提升是显而易见的。
【解决方案3】:

指针是要走的路。使用 fixed 关键字固定对象非常便宜,并且可以避免调用 WriteInt32 和 BlockCopy 等函数的开销。对于“通用解决方案”,您可以简单地使用 void* 并使用您自己的 memcpy (因为您正在处理少量数据)。但是指针不适用于真正的泛型。

【讨论】:

  • 那么您显然没有正确编写代码。你真的认为使用位移(每个 Int32 大约 8 条指令至少)比使用简单的 mov 指令更快吗?我说的是把缓冲区固定在循环之外。
  • 你能给我一个c#的例子吗?你说的是第二种 Set 方法,不是吗?我只需要普通值类型:Int16、UInt16、Int32、UInt32、Int64、UInt64
  • 固定 (byte* b = array) { for (...) (int)(b + offset) = value; } }。正是您在 Get 方法中所拥有的。如果您不相信这实际上是最快的方法,请查看“反汇编”窗口。
  • 啊,对;我弄错了棍子的一端。但要求是双向的。所以你必须对另一个有一个后备,以及一些抽象两者的机制。我会保持简单并编写班次。它还提出了“我是否需要检查/支持大端硬件”等问题。
  • 遗憾的是,C# 没有提供利用 bswap 指令来实现大/小端兼容性的方法,因此在这种情况下,您的解决方案将是最快的。至于 for 循环,它的意思是表明固定将在任何循环之外完成。
【解决方案4】:

您应该对代码进行一些分析,以揭示这是否是瓶颈。还查看您的代码,您似乎正在使用 .Net 函数调用将一个字节写入非托管数组,涉及内存上的引脚和对不安全代码的调用...

您可能最好声明一个 .Net System.IO.MemoryStream 并寻找和写入它,尽可能使用流编写器来推送您的更改,这应该使用更少的函数调用并且不需要不安全代码。如果您正在做诸如 DSP 之类的事情,您会发现指针在 C# 中更有用,您需要对数组中的每个值执行单个操作等。

编辑: 我还要提一下,根据您所做的事情,您可能会发现 CPU 缓存会生效,如果您可以继续处理适合缓存的单个小内存区域,那么您最终将获得最佳性能。

【讨论】:

  • 问题是它可能是一个瓶颈,因为应用程序正在与不同网络设备的负载进行通信并且在低成本机器上运行,其中一些设备使用繁重的协议,而另一些则不使用。您知道分析接口的好方法吗?问题将是网络的可变延迟..
猜你喜欢
  • 2012-05-01
  • 2012-06-18
  • 1970-01-01
  • 2013-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
相关资源
最近更新 更多