Я перебираю массив байтов и добавляю значения другого массива байтов в цикл 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];
        }

Есть ли способ ускорить процесс, чтобы он мог быть быстрее линейного.

2
Pavel 2 Май 2019 в 02:53

3 ответа

Лучший ответ

Вы можете использовать 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];
    }
}

Будет еще быстрее, если вы используете указатель для выравнивания одного из ваших массивов.

2
Cory Nelson 2 Май 2019 в 01:07

Увеличьте длину массива до следующего наибольшего значения, кратного 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);
      } 
   }
}
2
Strom 2 Май 2019 в 22:55

Вы можете использовать все свои процессоры / ядра, предполагая, что на вашей машине их больше одного.

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

Обновление: {{ Класс X0}} также можно использовать в .NET Framework. Для этого требуется пакет System.Numerics.Vectors. Он предлагает преимущество распараллеливания в одном ядре, выпуская одну инструкцию для нескольких данных (SIMD). Большинство современных процессоров поддерживают SIMD. Он включен только для 64-битных процессов, поэтому флаг [Предпочитать 32-битный] должен быть снят. В 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 раз быстрее, чем простой цикл, без использования более одного потока (25% потребления ЦП в 4-ядерном ПК).

2
Theodor Zoulias 25 Сен 2019 в 15:50