Странные результаты тестов для открытых методов mp

Результаты моих тестов очень странные. С одной стороны, у меня есть последовательная функция для вычисления квадратичной формы. С другой стороны, я написал две параллельные версии. Для одного потока все функции должны иметь примерно одинаковое время работы. Но на одну параллельную функцию нужно половину времени. Есть ли «скрытая» оптимизация?

Серийная версия:

double quadratic_form_serial(const std::vector<double> & A,const std::vector<double> & v, const std::vector<double> & w){
    int N= v.size();
    volatile double q=0.0;

    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            q +=v[i]*A[i*N+j]*w[j];

return q;
}

Параллельная версия 1:

double quadratic_form_parallel(const std::vector<double> & A,const std::vector<double> & v, const std::vector<double> & w, const int threadnum){
int N= v.size();

omp_set_num_threads(threadnum);
volatile double q[threadnum];
volatile double val = 0.0; 

#pragma omp parallel
{
    int me = omp_get_thread_num(); 
    q[me] = 0.0;
    #pragma omp for collapse(2)
    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            q[me]+=v[i]*A[i*N+j]*w[j];
    #pragma omp atomic
    val+=q[me];
}
return val;
}

Параллельная версия 2:

double quadratic_form_parallel2(const std::vector<double> & A,const std::vector<double> & v, const std::vector<double> & w, const int threadnum){
    int N= v.size();
    volatile double result =0.0;
    omp_set_num_threads(threadnum);

    #pragma omp parallel for reduction(+: result)
    for (int i=0; i<N; ++i)
        for (int j=0; j<N; ++j)
            result += v[i] * A[i*N + j] * w[j];
    return result;
}

Я запускаю код для N = 10000 и очищаю кеш перед вызовом функции. Функция quadratic_form_parallel2 требует с потоком один меньше половины времени, необходимого для двух других функций:

threads    serial       Parallel1       Parallel2
1          0.0882503    0.0875649       0.0313441   

Имейте в виду, что намного проще дать хороший ответ на ваш вопрос, если вы предоставите минимальный воспроизводимый пример и подробное описание вашего подхода к измерению (параметры компилятора, характеристики ЦП, инструкции по выполнению, ...)

Zulan 31.12.2018 02:57

Если бы вы писали на Фортране, у вас могло бы не возникнуть соблазна воспользоваться опрометчивым коллапсом внутреннего цикла. В зависимости от выбранных вами оптимизаций для вашего компилятора вы можете получить или не получить те же результаты, что и при умножении на v [i], явно перемещенном из внутреннего цикла. С gcc для оптимизации вам потребуется -ffast-math.

tim18 31.12.2018 11:25

Конечно, если бы вы писали даже грязный C++, внутренний цикл был бы заменен результатом + = v [i] * inner_product (& A [i * N], & A [i * N + N], w), поэтому вам снова следует обратите внимание на более чем одну причину, чтобы избежать коллапса. Как здесь часто упоминается, нет смысла делать выводы о производительности на неоптимизированном коде, хотя ваш вопрос больше о том, как вы предотвратили оптимизацию.

tim18 31.12.2018 11:36

Спасибо вам обоим! Я использовал флаги компилятора: CXXFLAGS = -Wall -std = C++ 11 -O3 -fopenmp и компилятор: g ++. Извините, я не совсем понял ту часть, которую вы упомянули с помощью collapse-clausel. Я считаю, что эта часть очень важна для меня при подготовке к экзамену. Не могли бы вы еще раз объяснить, почему коллапс-клаузель - плохая идея (здесь).

Suslik 31.12.2018 13:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
32
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Скорее всего, это результат того, что result является переменной reduction во второй версии OpenMP. Это означает, что каждый поток получает частную копию result, которая объединяется после параллельной области. Эта частная копия, вероятно, не соблюдает временные ограничения и, следовательно, может быть оптимизирована в большей степени. Я предполагаю, что подробное взаимодействие между volatile и private не указано.

Это показывает, что помечать переменную как volatile - предположительно, чтобы избежать оптимизации всего кода - это плохая идея. Вместо этого просто выведите результат.

Или назначьте окончательный результат для volatile, но да, используя переменную volatileвнутри, цикл будет разрушать производительность, вводя сохранение / перезагрузку и предотвращая автовекторизацию.

Peter Cordes 31.12.2018 07:25

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