Использование SIMD из C# в.NET Framework 4.6 работает медленнее

В настоящее время я пытаюсь вычислить сумму всех значений в огромном массиве, используя только 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);
}

1 ответ

Решение

Как указал @mike z, вам нужно убедиться, что вы находитесь в режиме релиза и нацелены на 64-битную версию, иначе RuyJIT, компилятор, поддерживающий SIMD, не будет работать (на данный момент он поддерживается только на 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, а затем увеличение производительности в 4 раза означает, что вы сравниваете векторизованную версию с относительно неоптимизированным кодом.

Это будет vals.Aggregate(0, (a, i) => a + i) часть в вашем случае. В общем, здесь есть много возможностей для оптимизации.

Когда я заменяю его тривиальным для цикла

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>() в каждой итерации.

Другие вопросы по тегам