Использование 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>()
в каждой итерации.