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

Vis*_*ish 4 .net c# 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));
Run Code Online (Sandbox Code Playgroud)

和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);
}
Run Code Online (Sandbox Code Playgroud)

小智 8

正如@mike z所指出的那样,你需要确保你处于发布模式并且以64位为目标,否则RuyJIT(支持SIMD的编译器)将无法工作(目前它只支持64位架构).在执行之前检查始终是一个很好的做法,使用:

Vector.IsHardwareAccelerated;
Run Code Online (Sandbox Code Playgroud)

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

yield return new Vector<int>(source, i);
Run Code Online (Sandbox Code Playgroud)

代替

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

这样,我使用随机生成的大型阵列设法将性能提高了近3.7倍.

此外,如果你改变你的方法,一个直接计算总和的方法,如果它得到了valew 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);


    }
Run Code Online (Sandbox Code Playgroud)

这里的表现更加显着.在我的测试中,我的性能提高了16倍vals.Aggregate(0, (a, i) => a + i).

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

这将是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;
}
Run Code Online (Sandbox Code Playgroud)

我只获得1.5倍的性能提升.尽管如此,考虑到操作的简单性,对于这种非常特殊的情况仍然是一种改进.

毋庸置疑,矢量化版本需要大型数组来克服new Vector<int>()每次迭代中创建的开销.