使用 C# 系统..数字.<T>用于解包/打包位的矢量



我正在测试.Net C# System.Numerics.Vector类的功能,用于打包和解包位。

我希望矢量按位左移/右移功能,但目前不可用,所以我尝试使用算术和逻辑方法模拟移位,如下所示。这是我看到的:

使用 Vector.Multiply(( 和 Vector.BitwiseOr(( 打包(模拟的按位 SHIFT LEFT 和 OR(比数组/指针代码稍差*。

*吞吐量降低 <10%(MB/秒(。

但是使用 Vector.Divide(( 和 Vector.BitwiseAnd(( 解包(模拟的按位 SHIFT RIGHT 和 AND(比数组/指针代码差得多**。

**吞吐量降低 50%

铌:

  • 矢量使用单位进行测试(这也在评论中提出(。

  • 测试基础是将100Mn到1Bn整数打包和解包,以65536个整数块为单位。我为每个块随机生成了 int[]。

  • 我还测试了按位(& |>> <<(以及算术(+ - */(运算,发现成本没有明显差异。均匀除法并没有那么糟糕,在整个与乘法中只有 10% 的降级(除法问题在评论中提出(

  • 我将原始测试代码(用于非 Vector 比较(更改为不安全/指针例程,以便在打包(一个单词的许多整数(与解包(一个单词到多个整数(方面创建更多的同类测试。这使得非矢量代码的整个(打包和解包之间(的差异降低到<5%。(这与我对下面编译器和优化的评论相反(

  • 非优化载体:包装速度是拆包速度的 2 倍

  • 优化的矢量:在包装方面提高了
  • 4 倍(与非优化的矢量相比(,在拆包方面提高了 2 倍

  • 非优化数组/指针:解包比打包快 ~5%

  • 优化的阵列/指针:打包改进了
  • 3 倍(与未优化的阵列指针相比(,拆包改进了 2.5 倍。总体而言,优化的数组/指针打包比优化的数组/指针解包快 <5%。

  • 优化的
  • 数组/指针打包比优化的矢量打包快 ~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);

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

您可以使用link.exe中的/LTCG(链接时代码生成(选项,将从 IL (ildasm.exe( 和C#(csc.exe( 生成的中间.netmodule二进制文件链接到单个程序集中。

运行时 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

>Vector.Divide没有整数类型的硬件加速。它非常慢。

直到.NET 7.0,Vector才增加了ShiftRightArithmetic,ShiftRightLogic方法。

我开发了VectorTraits库。它允许较低版本的.NET程序(.NET Core 3.0+,.NET 5.0+(使用硬件加速的ShiftRightArithmetic,ShiftRightLogic方法。 https://www.nuget.org/packages/VectorTraits

最新更新