Coc*_*lla 6 c# simd intrinsics .net-core .net-core-3.0
我正在使用System.Runtime.Intrinsics命名空间中的.NET Core 3.0对硬件内在函数的新支持。
我有一些代码可以在一个循环中执行4个XOR操作-下面是一个简化的示例(我没有在IDE中编写此代码,因此请忽略任何语法错误:
private static unsafe ulong WyHashCore(byte[] array)
{
fixed (byte* pData = array)
{
byte* ptr = pData;
// Consume 32-byte chunks
for (int i = 0; i < array.Length; i += 32)
{
ulong a = Read64(ptr, i);
ulong b = Read64(ptr, i + 8);
ulong c = Read64(ptr, i + 16);
ulong d = Read64(ptr, i + 24);
// XOR them with some constants
ulong xor1 = a ^ SOME_CONSTANT1;
ulong xor2 = b ^ SOME_CONSTANT2;
ulong xor3 = c ^ SOME_CONSTANT3;
ulong xor4 = d ^ SOME_CONSTANT4;
// Use the resulting values
}
}
}
Run Code Online (Sandbox Code Playgroud)
该Read64方法如下所示:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe ulong Read64(byte* ptr, int start)
=> *(ulong*)(ptr + start);
Run Code Online (Sandbox Code Playgroud)
我尝试用以下方式替换4个XOR行:
byte[] array; // An array from elsewhere
private static unsafe ulong WyHashCore(byte[] array)
{
var bVector = Vector256.Create(SOME_CONSTANT1, SOME_CONSTANT2, SOME_CONSTANT3, SOME_CONSTANT4);
fixed (byte* pData = array)
{
byte* ptr = pData;
// Consume 32-byte chunks
for (int i = 0; i < array.Length; i += 32)
{
ulong a = Read64(ptr, i);
ulong b = Read64(ptr, i + 8);
ulong c = Read64(ptr, i + 16);
ulong d = Read64(ptr, i + 24);
// Create a 256-bit vector from the 4 64-bit integers
var aVector = Vector256.Create(a, b, c, d);
// XOR the 2 vectors
var res = Avx2.Xor(aVector, bVector);
// Get the resulting values out of the result vector
ulong xor1 = res.GetElement(0);
ulong xor2 = res.GetElement(1);
ulong xor3 = res.GetElement(2);
ulong xor4 = res.GetElement(3);
// Use the resulting values
}
}
}
Run Code Online (Sandbox Code Playgroud)
这确实可以达到预期的结果-但比只乘标量要慢 15倍!
我在某个地方出错或滥用SIMD吗?
**更新**我已经更新了代码,以使用“正确”的方式将数据加载到向量或从向量中卸载数据,现在它的速度比标量代码快了3.75倍!
byte[] array; // An array from elsewhere
private static readonly Vector256<ulong> PrimeVector = Vector256.Create(SOME_CONSTANT1, SOME_CONSTANT2, SOME_CONSTANT3, SOME_CONSTANT4);
private static unsafe ulong WyHashCore(byte[] array)
{
// Create space on the stack to hold XOR results
var xorResult = stackalloc ulong[4];
fixed (byte* pData = array)
{
byte* ptr = pData;
// Consume 32-byte chunks
for (int i = 0; i < array.Length; i += 32)
{
// Create a 256-bit vector from the 4 64-bit integers
var vector = Avx.LoadVector256((ulong*)(ptr + i));
// XOR the 2 vectors
var res = Avx2.Xor(vector, PrimeVector);
// Store the resulting vector in memory
Avx2.Store(xorResult, res);
// Get the resulting values out of the result vector
var xor1 = *xorResult;
var xor2 = *(xorResult + 1);
var xor3 = *(xorResult + 2);
var xor4 = *(xorResult + 3);
// Use the resulting values
}
}
}
Run Code Online (Sandbox Code Playgroud)
TL;DR AVX2 硬件内部函数使用不正确,导致生成效率非常低的 SIMD 代码。
错误在于指令在缓冲区中加载、操作和存储数据的方式。该操作应使用 AVX/AVX2 Avx2.X 或具有内存的内部函数来执行,这将使加载时间加快 4 倍并返回 Vector256。另一方面,这将使对 Vector256.Create 的调用变得多余,并进一步加快执行速度。最后,应使用 Avx2.Store() 内部函数将数据存储在数组中。这又会将代码速度提高大约 4 倍。
应该应用的最后一个优化是利用 CPU 指令级并行性。通常,SIMD 指令在预定义数量的 CPU 周期内执行,延迟可能大于 1 个 CPU 周期。这些参数是特定于 CPU 的,可以在以下位置找到:
由于所有可以应用的优化都非常复杂,我稍后会在更长的文章中解释它们,但总的来说,与您正在处理的问题的基本情况相比,我预计由于矢量化,速度会提高 4 倍。
您使用的代码示例是一个简单的循环,以四无符号四字步骤修改数据,并且是通过优化编译器进行自动向量化的完美候选者。当 GCC 9.1 使用选项-O3 -march=haswell优化相同的 C++ 循环时,生成的机器代码显示应用于循环的所有标准优化:
#include <cstdint>
void hash(uint64_t* buffer, uint64_t length) {
uint64_t* pBuffer = buffer;
const uint64_t CONST1 = 0x6753ul;
const uint64_t CONST2 = 0x7753ul;
const uint64_t CONST3 = 0x8753ul;
const uint64_t CONST4 = 0x9753ul;
for(uint64_t i = 0; i < length; i += 4)
{
*pBuffer ^= CONST1;
*(pBuffer + 1) ^= CONST2;
*(pBuffer + 2) ^= CONST3;
*(pBuffer + 3) ^= CONST4;
}
}
Run Code Online (Sandbox Code Playgroud)
test rsi, rsi
je .L11
cmp rsi, -4
ja .L6
lea rdx, [rsi-1]
vmovdqa ymm1, YMMWORD PTR .LC0[rip]
xor eax, eax
shr rdx, 2
inc rdx
.L5:
vpxor ymm0, ymm1, YMMWORD PTR [rdi]
inc rax
add rdi, 32
vmovdqu YMMWORD PTR [rdi-32], ymm0
cmp rax, rdx
jb .L5
vzeroupper
.L11:
ret
.L6:
vmovdqa ymm1, YMMWORD PTR .LC0[rip]
xor eax, eax
.L3:
vpxor ymm0, ymm1, YMMWORD PTR [rdi]
add rax, 4
add rdi, 32
vmovdqu YMMWORD PTR [rdi-32], ymm0
cmp rsi, rax
ja .L3
vzeroupper
jmp .L11
.LC0:
.quad 26451
.quad 30547
.quad 34643
.quad 38739
Run Code Online (Sandbox Code Playgroud)
.LCPI0_0:
.quad 26451 # 0x6753
.quad 30547 # 0x7753
.quad 34643 # 0x8753
.quad 38739 # 0x9753
hash(unsigned long*, unsigned long): # @hash(unsigned long*, unsigned long)
test rsi, rsi
je .LBB0_3
xor eax, eax
vmovaps ymm0, ymmword ptr [rip + .LCPI0_0] # ymm0 = [26451,30547,34643,38739]
.LBB0_2: # =>This Inner Loop Header: Depth=1
vxorps ymm1, ymm0, ymmword ptr [rdi + 8*rax]
vmovups ymmword ptr [rdi + 8*rax], ymm1
add rax, 4
cmp rax, rsi
jb .LBB0_2
.LBB0_3:
vzeroupper
ret
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
260 次 |
| 最近记录: |