c# - 在 .NET Framework 4.6 中从 C# 使用 SIMD 操作速度较慢

标签 c# .net ryujit

我目前正在尝试仅使用 C# 计算一个巨大数组中所有值的总和,并使用 SIMD 来比较性能,而 SIMD 版本要慢得多。请查看下面的代码片段,如果我遗漏了什么请告诉我。 “vals”是从图像文件中读取并省略该部分以保持精简的巨大数组。

var watch1 = new Stopwatch();
watch1.Start();
var total = vals.Aggregate(0, (a, i) => a + i);
watch1.Stop();
Console.WriteLine(string.Format("Total is: {0}", total));
Console.WriteLine(string.Format("Time taken: {0}", watch1.ElapsedMilliseconds));

var watch2 = new Stopwatch();
watch2.Start();
var sTotal = GetSIMDVectors(vals).Aggregate((a, i) => a + i);
int sum = 0;
for (int i = 0; i < Vector<int>.Count; i++)
    sum += sTotal[i];
watch2.Stop();
Console.WriteLine(string.Format("Another Total is: {0}", sum));
Console.WriteLine(string.Format("Time taken: {0}", watch2.ElapsedMilliseconds));

和 GetSIMDVectors 方法

private static IEnumerable<Vector<int>> GetSIMDVectors(short[] source)
{
    int vecCount = Vector<int>.Count;
    int i = 0;
    int len = source.Length;
    for(i = 0; i + vecCount < len; i = i + vecCount)
    {
        var items = new int[vecCount];
        for (int k = 0; k < vecCount; k++)
        {
            items[k] = source[i + k];
        }
        yield return new Vector<int>(items);
    }
    var remaining = new int[vecCount];
    for (int j = i, k =0; j < len; j++, k++)
    {
        remaining[k] = source[j];
    }
    yield return new Vector<int>(remaining);
}

最佳答案

正如@mike z 所指出的,您需要确保您处于 Release模式并以 64 位为目标,否则支持 SIMD 的编译器 RuyJIT 将无法工作(目前它仅支持 64 位架构)。 在执行之前进行检查始终是遵循使用的好习惯:

Vector.IsHardwareAccelerated;

此外,您无需在创建向量之前先使用 for 循环创建数组。您只需使用 vector<int>(int[] array,int index) 从原始源数组创建向量构造函数。

yield return new Vector<int>(source, i);

代替

var items = new int[vecCount];
for (int k = 0; k < vecCount; k++)
{
    items[k] = source[i + k];
}
yield return new Vector<int>(items);

通过这种方式,我使用随机生成的大型数组成功地将性能提高了将近 3.7 倍

此外,如果您要将方法更改为一旦获得 new Vector<int>(source, i) 的值就直接计算总和的方法,像这样:

private static int GetSIMDVectorsSum(int[] source)
    {
        int vecCount = Vector<int>.Count;
        int i = 0;
        int end_state = source.Length;

        Vector<int> temp = Vector<int>.Zero;


        for (; i < end_state; i += vecCount)
        {
            temp += new Vector<int>(source, i);

        }

        return Vector.Dot<int>(temp, Vector<int>.One);


    }

此处的性能提升更为显着。我设法将性能提高了 16 倍 vals.Aggregate(0, (a, i) => a + i)在我的测试中。

但是,从理论上来说,如果例如Vector<int>.Count返回 4,那么任何高于 4x 的性能提升都表明您正在将矢量化版本与相对未优化的代码进行比较。

那将是 vals.Aggregate(0, (a, i) => a + i)在你的情况下的一部分。所以基本上,您在这里有很大的优化空间。

当我用一个简单的 for 循环替换它时

private static int no_vec_sum(int[] vals)
{
    int end = vals.Length;
    int temp = 0;

    for (int i = 0; i < end; i++)
    {
        temp += vals[i];
    }
    return temp;
}

我的性能只提高了 1.5 倍。不过,对于这种非常特殊的情况,考虑到操作的简单性,这仍然是一个改进。

不用说,矢量化版本需要数组来克服创建new Vector<int>() 引起的开销。在每次迭代中。

关于c# - 在 .NET Framework 4.6 中从 C# 使用 SIMD 操作速度较慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31999479/

相关文章:

c# - 为什么EF会从数据库加载数据而忽略本地变化?

.net - 文本提要的正则表达式替换

.net - 3分钟了解什么是反射?

c# - 订阅 ObservableCollection 项目属性已更改 - WPF

c# - LINQ 的字数统计

c# - async wait 与 TaskFactory.StartNew 和 WaitAll

c# - 确认消息不提示

.net - RyuJIT 和 Roslyn 有什么区别?

.net - .NET JIT编译器在什么条件下执行自动矢量化?