Проблема не в ваших встроенных функциях AVX, давайте посмотрим на код без встроенных функций на минуту:
void mv(double *a,double *b,double *c, int m, int n, int l)
{
#pragma omp parallel for schedule(static)
for (int k = 0; k < l; k++) {
double xb = b[k];
for (int i = 0; i < m; i++) {
double xa = a[m*k+i];
double xc = c[i];
xc = xc + xa * xb;
c[i] = xc;
}
}
}
Примечание: ваше личное объявление было технически правильным и избыточным, потому что оно было объявлено внутри параллельного цикла, но гораздо проще рассуждать о коде, если вы объявляете переменные как можно более локально.
Состояние гонки в вашем коде - c[i]
- который пытаются обновить несколько потоков. Теперь, даже если бы вы могли защитить это, скажем, с помощью атомарного обновления, производительность была бы ужасной: не только из-за защиты, но и потому, что данные c[i]
должны постоянно перемещаться между кэшами разных ядер.
Одна вещь, которую вы можете сделать с этим, это использовать сокращение массива на c
. Это делает личную копию c
для каждого потока, и в конце они объединяются:
void mv(double *a,double *b,double *c, int m, int n, int l)
{
#pragma omp parallel for schedule(static) reduction(+:c[:m])
for (int k = 0; k < l; k++) {
for (int i = 0; i < m; i++) {
c[i] += a[m*k+i] * b[k];
}
}
}
Это должно быть достаточно эффективным, если два m
-вектора вписываются в ваш кеш, но вы все равно можете получить много накладных расходов из-за накладных расходов на управление потоками. В конечном итоге вы будете ограничены пропускной способностью памяти, потому что при умножении векторной матрицы у вас есть только одно вычисление на элемент, считанный из a
.
В любом случае, вы, конечно, можете поменять местами циклы i
и k
и сохранить сокращение, но тогда ваш шаблон доступа к памяти на a
будет неэффективным (пошаговым) - так что вам следует block цикл, чтобы избежать этого.
Теперь, если вы посмотрите на вывод любого современного компилятора , он сгенерирует SIMD-код сам по себе. Конечно, вы можете применить свои собственные SIMD-функции, если хотите. Но убедитесь, что вы правильно обрабатываете крайние случаи, если m
не делится на 4 (вы не делали это в исходной версии).
В конце концов, если вы действительно хотите повысить производительность - используйте функции из библиотеки BLAS (например, MKL). Если вы хотите поэкспериментировать с оптимизацией, у вас будет множество возможностей углубиться в детали.