我正在测试 .Net C# System.Numerics.Vector 类打包和解包位的功能。
我希望有向量按位左移/右移功能,但目前不可用,因此我尝试使用算术和逻辑方法来模拟移位,如下所示。这是我所看到的:
使用 Vector.Multiply() 和 Vector.BitwiseOr() 进行打包(模拟按位 SHIFT LEFT 和 OR)比数组/指针代码稍差一些*。
*<10% degradation in throughput (MB/sec).
但是使用 Vector.Divide() 和 Vector.BitwiseAnd() 解包(模拟按位右移和与)比数组/指针代码差得多**。
**吞吐量下降 50%
注意:
Vector 使用单位进行了测试(这也在评论中提出)。
测试基础是将 100Mn 至 1Bn 整数打包和拆包到 65536 个整数的块中。我为每个块随机生成了 int[]。
我还测试了按位 (& | >> <<) as well as arithmetic (+ - * /) operations and saw no marked difference in cost. Even divide was not that bad with only a 10% degradation in throughout vs multiply (the question of division was raised in comments)
我将原始测试代码(用于非向量比较)更改为不安全/指针例程,以在打包(许多整数到一个单词)与解包(一个单词到一个单词)方面创建更多类似的测试许多整数)。这使得非矢量代码的吞吐量(打包和解包之间)差异降至 <5%. (which counters my comment about the compiler and optimization below)
非优化向量:打包速度是拆包速度的 2 倍
优化向量:打包方面提高了 4 倍(与非优化向量相比),拆包方面提高了 2 倍
未优化的数组/指针:解包比打包快约 5%
优化的数组/指针:打包方面提高了 3 倍(与未优化的数组指针相比),拆包方面提高了 2.5 倍。总的来说,优化的数组/指针打包是 <5% faster than Optimized array/pointer unpacking.
优化的数组/指针打包比优化的向量包快约 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);
伊尔
/// 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);
};
您可以使用 中的 ildasm.exe
(链接时代码生成)选项,将从 IL
(
csc.exe
) 和 C#(
/LTCG
) 生成的中间.netmodule
二进制文件链接到单个程序集中。 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
Vector.Divide
对于整数类型没有硬件加速。速度很慢。
直到
.NET 7.0
,Vector 才添加了 ShiftRightArithmetic、ShiftRightLogical 方法。
我开发了 VectorTraits 库。它允许较低版本的
.NET
程序(.NET Core 3.0
+、.NET 5.0
+)使用硬件加速的 ShiftRightArithmetic、ShiftRightLogical 方法。
https://www.nuget.org/packages/VectorTraits