我看到了一些文章描述了如何使用JIT内置函数实现
我决定尝试重写一个方法,以查看是否能够获得一些加速,但目前为止,向量化代码运行速度比原始代码慢3倍,我不确定原因。以下是两个版本的一个方法,检查两个
正如我所说,我使用BenchmarkDotNet测试了两个版本,使用
我是否遗漏了一些显而易见的东西?
谢谢!
编辑:我使用不安全代码并尽可能地优化此代码,而不是将其并行化的原因是该方法已经从
此外,能够在多个线程上并行处理代码通常不是让单个并行任务未经优化的好理由。
Vector<T>
的SIMD功能,因此在使用它时,编译器会正确输出AVS/SSE等指令,比传统的线性循环(例如这里)更快。我决定尝试重写一个方法,以查看是否能够获得一些加速,但目前为止,向量化代码运行速度比原始代码慢3倍,我不确定原因。以下是两个版本的一个方法,检查两个
Span<float>
实例是否具有相同位置上共享相同阈值的所有成对项。// Classic implementation
public static unsafe bool MatchElementwiseThreshold(this Span<float> x1, Span<float> x2, float threshold)
{
fixed (float* px1 = &x1.DangerousGetPinnableReference(), px2 = &x2.DangerousGetPinnableReference())
for (int i = 0; i < x1.Length; i++)
if (px1[i] > threshold != px2[i] > threshold)
return false;
return true;
}
// Vectorized
public static unsafe bool MatchElementwiseThresholdSIMD(this Span<float> x1, Span<float> x2, float threshold)
{
// Setup the test vector
int l = Vector<float>.Count;
float* arr = stackalloc float[l];
for (int i = 0; i < l; i++)
arr[i] = threshold;
Vector<float> cmp = Unsafe.Read<Vector<float>>(arr);
fixed (float* px1 = &x1.DangerousGetPinnableReference(), px2 = &x2.DangerousGetPinnableReference())
{
// Iterate in chunks
int
div = x1.Length / l,
mod = x1.Length % l,
i = 0,
offset = 0;
for (; i < div; i += 1, offset += l)
{
Vector<float>
v1 = Unsafe.Read<Vector<float>>(px1 + offset),
v1cmp = Vector.GreaterThan<float>(v1, cmp),
v2 = Unsafe.Read<Vector<float>>(px2 + offset),
v2cmp = Vector.GreaterThan<float>(v2, cmp);
float*
pcmp1 = (float*)Unsafe.AsPointer(ref v1cmp),
pcmp2 = (float*)Unsafe.AsPointer(ref v2cmp);
for (int j = 0; j < l; j++)
if (pcmp1[j] == 0 != (pcmp2[j] == 0))
return false;
}
// Test the remaining items, if any
if (mod == 0) return true;
for (i = x1.Length - mod; i < x1.Length; i++)
if (px1[i] > threshold != px2[i] > threshold)
return false;
}
return true;
}
正如我所说,我使用BenchmarkDotNet测试了两个版本,使用
Vector<T>
的版本比另一个版本慢大约3倍。我尝试使用不同长度的区间运行测试(从大约100到2000以上),但向量化方法仍然比另一个版本慢得多。我是否遗漏了一些显而易见的东西?
谢谢!
编辑:我使用不安全代码并尽可能地优化此代码,而不是将其并行化的原因是该方法已经从
Parallel.For
迭代中调用。此外,能够在多个线程上并行处理代码通常不是让单个并行任务未经优化的好理由。
Parallel.For
,这个方法实际上将在每个并行迭代中被调用。 - Sergio0694unsafe
和指针就像与语言作斗争。 - user585968