【问题标题】:Optimize summing 2 arrays of bytes优化求和 2 个字节数组
【发布时间】:2019-09-20 11:32:09
【问题描述】:

我正在遍历一个字节数组并在 for 循环中添加另一个字节数组的值。

        var random = new Random();
        byte[] bytes = new byte[20_000_000]; 
        byte[] bytes2 = new byte[20_000_000];

        for (int i = 0; i < bytes.Length; i++)
        {
            bytes[i] = (byte)random.Next(255);
        }

        for (int i = 0; i < bytes.Length; i++)
        {
            bytes2[i] = (byte)random.Next(255);
        }

        //how to optimize the part below
        for (int i = 0; i < bytes.Length; i++)
        {
            bytes[i] += bytes2[i];
        }

有什么办法可以加快这个过程,所以可以比线性快。

【问题讨论】:

  • 您是要连接两个字节数组,还是添加一个字节数组的每个元素?
  • @juharr 添加了代码。
  • @CoryNelson 添加每个元素。

标签: c# arrays performance opencv


【解决方案1】:

你可以使用Vector:

static void Add(Span<byte> dst, ReadOnlySpan<byte> src)
{
    Span<Vector<byte>> dstVec = MemoryMarshal.Cast<byte, Vector<byte>>(dst);
    ReadOnlySpan<Vector<byte>> srcVec = MemoryMarshal.Cast<byte, Vector<byte>>(src);

    for (int i = 0; i < dstVec.Length; ++i)
    {
        dstVec[i] += srcVec[i];
    }

    for (int i = dstVec.Length * Vector<byte>.Count; i < dst.Length; ++i)
    {
        dst[i] += src[i];
    }
}

如果您在此处使用指针来对齐其中一个数组,将会更快。

【讨论】:

  • 想指出这需要 .NET Core >= 2.1 或 .NET Standard >= 2.1。 MemoryMarshal 在 .NET Framework 上不可用。
  • 它提供了 5 倍的提升!
【解决方案2】:

将数组长度填充到下一个最大的 8 倍数。(在您的示例中已经存在。)

使用不安全的上下文创建两个ulong 数组,指向现有字节数组的开头。使用 for 循环迭代 bytes.Length / 8 次,每次添加 8 个字节。

在我的系统上,它的运行时间不到 13 毫秒。与原始代码的 105 毫秒相比。

您必须添加/unsafe 选项才能使用此代码。打开项目属性,选择“允许不安全代码”。

var random = new Random();
byte[] bytes = new byte[20_000_000]; 
byte[] bytes2 = new byte[20_000_000];




int Len = bytes.Length >> 3; // >>3 is the same as / 8

ulong MASK =    0x8080808080808080;
ulong MASKINV = 0x7f7f7f7f7f7f7f7f;

//Sanity check
if((bytes.Length & 7) != 0) throw new Exception("bytes.Length is not a multiple of 8");
if((bytes2.Length & 7) != 0) throw new Exception("bytes2.Length is not a multiple of 8");

unsafe
{
    //Add 8 bytes at a time, taking into account overflow between bytes
   fixed (byte* pbBytes = &bytes[0])
   fixed (byte* pbBytes2 = &bytes2[0])
   {
      ulong* pBytes = (ulong*)pbBytes;
      ulong* pBytes2 = (ulong*)pbBytes2;
      for (int i = 0; i < Len; i++)
      {
        pBytes[i] = ((pBytes2[i] & MASKINV) + (pBytes[i] & MASKINV)) ^ ((pBytes[i] ^ pBytes2[i]) & MASK);
      } 
   }
}

【讨论】:

  • 改变bytes2的内容对你的算法真的有必要吗?
  • @PetSerAl,如果 byte2 的第 8 位未设置为 0,则可能由于进位而溢出到下一个字节的低位。谢谢,逻辑是必要的,分配是没有的。 25 毫秒 -> 12 毫秒。新代码有点难读。
  • @Strom 有没有办法保持溢出值,所以如果需要我可以计算平均值。
  • @Strom 如果我们将 byte[] 转换为 short[] 并在 sum 之后将其转换回 byte[] 会怎样。 MASK/MASKINV 将如何变化?
  • @Pavel,ushort 值的掩码为 0x8000800080008000,反之为 0x7fff7fff7fff7fff, 1。这样会损失一半的效率。 2. 不需要掩码(如果每个操作数的最高 1 小于第 15 位)。
【解决方案3】:

假设您的机器有多个处理器/内核,您可以利用所有处理器/内核。

Parallel.ForEach(Partitioner.Create(0, bytes.Length), range =>
{
    for (int i = range.Item1; i < range.Item2; i++)
    {
        bytes[i] += bytes2[i];
    }
});

更新:Vector&lt;T&gt; 类也可以在 .NET Framework 中使用。它需要包System.Numerics.Vectors。它通过向多个数据发出单指令 (SIMD) 提供了单核并行化的优势。当前的大多数处理器都支持 SIMD。它仅对 64 位进程启用,因此必须取消选中 [Prefer 32-bit] 标志。在 32 位进程上属性Vector.IsHardwareAccelerated 返回false,性能很差。

using System.Numerics;

/// <summary>Adds each pair of elements in two arrays, and replaces the
/// left array element with the result.</summary>
public static void Add_UsingVector(byte[] left, byte[] right, int start, int length)
{
    int i = start;
    int step = Vector<byte>.Count; // the step is 16
    int end = start + length - step + 1;
    for (; i < end; i += step)
    {
        // Vectorize 16 bytes from each array
        var vector1 = new Vector<byte>(left, i);
        var vector2 = new Vector<byte>(right, i);
        vector1 += vector2; // Vector arithmetic is unchecked only
        vector1.CopyTo(left, i);
    }
    for (; i < start + length; i++) // Process the last few elements
    {
        unchecked { left[i] += right[i]; }
    }
}

这比简单循环的运行速度快 4-5 倍,无需使用多个线程(在 4 核 PC 中 CPU 消耗为 25%)。

【讨论】:

  • 我用有关在 .NET Framework 中使用 Vector&lt;T&gt; 类的信息更新了我的答案。
猜你喜欢
  • 2017-05-20
  • 1970-01-01
  • 2021-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-26
  • 2013-09-26
  • 2023-03-24
相关资源
最近更新 更多