Использование C # System..Numerics.Vector <T>для распаковки / упаковки битов - PullRequest
0 голосов
/ 19 мая 2018

Я тестирую возможности класса .Net C # System.Numerics.Vector для упаковки и распаковки битов.

Я надеялся на векторную побитовую сдвиг влево / вправо, но в настоящее время она недоступна, поэтомуЯ попытался смоделировать сдвиг, используя арифметические и логические методы, как показано ниже.Вот что я увидел:

Упаковка (имитация побитового сдвига влево и ИЛИ) с использованием Vector.Multiply () и Vector.BitwiseOr () немного хуже *, чем код массива / указателя.

*<10% снижение пропускной способности (МБ / с). </p>

Но распаковка (имитация побитового сдвига вправо и AND) с использованием Vector.Divide () и Vector.BitwiseAnd () намного хуже **, чем массив / указателькод.

** Падение пропускной способности на 50%

Примечание:

  • Вектор был протестирован с использованием модуля (это также поднималось в комментариях).

  • Основой теста была упаковка и распаковка целых чисел от 100Mn до 1Bn в блоки по 65536 целых чисел.Я случайным образом сгенерировал int [] для каждого блока.

  • Я также проверял побитовые (& | >> <<) и арифметические (+ - * /) операции и не видел помеченныхразница в стоимости.Даже разделение было не так уж и плохо, всего лишь 10% ухудшение по сравнению с умножением (вопрос разделения был поднят в комментариях) </p>

  • Я изменил свой исходный код теста (для не-Vectorсравнение) с небезопасной подпрограммой / указателем, чтобы создать больше подобного теста с точки зрения упаковки (много целых чисел к слову) по сравнению с распаковкой (слово ко многим целым числам).Это привело к разнице во всем (между упаковкой и распаковкой) для не-векторного кода до дисперсии <5%.(что противоречит моему комментарию о компиляторе и оптимизации ниже) </p>

  • Неоптимизированный вектор: упаковка в 2 раза быстрее распаковки

  • Оптимизированный вектор: получено 4-кратное улучшение (по сравнению с неоптимизированным вектором) в упаковке и 2-кратное улучшение при распаковке

  • Неоптимизированный массив / указатель: распаковка происходит на ~ 5% быстрее, чем упаковка

  • Оптимизированный массив / указатель: получено 3-кратное улучшение (по сравнению с неоптимизированным указателем массива) для упаковки и 2,5-кратное улучшение для распаковки.В целом оптимизированная упаковка массивов / указателей была <5% быстрее, чем оптимизированная распаковка массивов / указателей. </p>

  • Оптимизированная упаковка массивов / указателей была на ~ 10% быстрее, чем пакет оптимизированного вектора

Заключение на данный момент:

  • Vector.Divide () представляется сравнительно более медленной реализацией по сравнению с обычным арифметическим делением

  • Кроме того, компилятор, по-видимому, не оптимизирует код Vector.Divide () до уровня, близкого к Vector.Multiply () (который поддерживает приведенные ниже комментарии относительно оптимизации деления)

  • Обработка массива / указателя в настоящее время немного быстрее, чем класс Vector для упаковки данных, и значительно быстрее для распаковки.

  • System.Numerics требуется Vector.ShiftLeft () & Vector.ShiftRight () методы

Вопрос (обновлено);

  • мой вывод примерно верен?или есть другие аспекты для проверки / рассмотрения?

Дополнительная информация:

int numPages =  8192; // up to >15K     
int testSize = 65536;
StopWatch swPack = new StopWatch();
StopWatch swUnpack = new StopWatch();
long byteCount = 0;
for (int p = 0; p < numpages; b++)
{
    int[] data = GetRandomIntegers(testSize, 14600, 14800);

    swPack.Start();
    byte[] compressedBytes = pack(data);
    swPack.Stop();

    swUnpack.Start();
    int[] unpackedInts = unpack(compressedBytes);
    swUnpack.Stop();

    byteCount += (data.Length*4);

}
Console.WriteLine("Packing Throughput (MB/sec): " + byteCount / 1000 / swPack.ElapsedMilliseconds);
Console.WriteLine("Unpacking Throughput (MB/sec): " + byteCount / 1000 / swUnpacking.ElapsedMilliseconds);

1 Ответ

0 голосов
/ 15 июня 2019

IL

/// non-SIMD fallback implementation for 128-bit right-shift (unsigned)
/// n: number of bit positions to right-shift a 16-byte memory image.
/// Vector(T) argument 'v' is passed by-ref and modified in-situ.
/// Layout order of the two 64-bit quads is little-endian.

.method public static void SHR(Vector_T<uint64>& v, int32 n) aggressiveinlining
{
    ldarg v
    dup
    dup
    ldc.i4.8
    add
    ldind.i8
    ldc.i4.s 64
    ldarg n
    sub
    shl

    ldarg v
    ldind.i8
    ldarg n
    shr.un

    or
    stind.i8

    ldc.i4.8
    add
    dup
    ldind.i8
    ldarg n
    shr.un
    stind.i8

    ret
}

псевдокод

As<Vector<ulong>,ulong>(ref v) = (As<Vector<ulong>,ulong>(in v) >> n) | 
                                  (ByteOffsAs<Vector<ulong>,ulong>(in v, 8) << (64 - n));
ByteOffsAs<Vector<ulong>,ulong>(ref v, 8) >>= n;

C # внешняя декларация

static class vector_ext
{
    [MethodImpl(MethodImplOptions.ForwardRef | MethodImplOptions.AggressiveInlining)]
    extern public static void SHR(ref Vector<ulong> v, int n);
};

Вы можете связать промежуточные .netmodule двоичные файлы, полученные из IL (ildasm.exe) и C # (csc.exe) вместе в одну сборку, используя/LTCG (генерация кода времени ссылки) в link.exe.

время выполнения x64 JIT (.NET Framework 4.7.2)

0x7FF878F5C7E0    48 89 4C 24 08       mov qword ptr [rsp+8],rcx
0x7FF878F5C7E5    8B C2                mov eax,edx
0x7FF878F5C7E7    F7 D8                neg eax
0x7FF878F5C7E9    8D 48 40             lea ecx,[rax+40h]
0x7FF878F5C7EC    48 8B 44 24 08       mov rax,qword ptr [rsp+8]
0x7FF878F5C7F1    4C 8B 40 08          mov r8,qword ptr [rax+8]
0x7FF878F5C7F5    49 D3 E0             shl r8,cl
0x7FF878F5C7F8    4C 8B 08             mov r9,qword ptr [rax]
0x7FF878F5C7FB    8B CA                mov ecx,edx
0x7FF878F5C7FD    49 D3 E9             shr r9,cl
0x7FF878F5C800    4D 0B C1             or  r8,r9
0x7FF878F5C803    4C 89 00             mov qword ptr [rax],r8
0x7FF878F5C806    48 83 C0 08          add rax,8
0x7FF878F5C80A    8B CA                mov ecx,edx
0x7FF878F5C80C    48 D3 28             shr qword ptr [rax],cl
0x7FF878F5C80F    C3                   ret
...