параллельный openmp для работы с не-модулями

#c #multithreading #thread-safety #openmp

#c #многопоточность #потокобезопасность #openmp

Вопрос:

Я пытаюсь ускорить программу, в основе которой лежит тривиально выглядящий цикл:

 double sum=0.;
#pragma omp parallel for reduction( :sum) // fails
for( size_t i=0; i<_S.size();   i ){
  sum  = _S[i].first* R(atoms,_S[i].second) ;
}
  

Хотя сам цикл тривиален, объекты внутри него не являются модулями: Here _S на самом деле является
std::vector< std::pair<double, std::vector<size_t> > > , и R(...) является перегруженным operator(...) const некоторым объектом. Оба его аргумента квалифицируются как const , так что вызов не имеет никаких побочных эффектов.

Поскольку около 90% времени выполнения тратится на этот вызов, казалось простым добавить OpenMP pragma, как показано выше, и получить ускорение в два или три раза; но, конечно, — код нормально работает с одним потоком, но выдает явно неправильные результаты для более чем одного потока :-).

Нет зависимости от данных, оба _S и R(...) кажутся безопасными для совместного использования между потоками, но все равно это приводит к бессмыслице.

Я был бы очень признателен за любые указания на то, как найти, что идет не так.

UPD2:

Понял это. Как и все ошибки, это тривиально. R(...) Вызывал operator() что-то в этом роде:

 class objR{
 public:
   objR(const size_t N){
     _buffer.reserve(N);
   };
   double operator(...) const{
     // do something, using the _buffer to store intermediaries
   }

 private:
   std::vector<double> _buffer;
};
  

Очевидно, что разные потоки используют _buffer одновременно и все портят. Мое решение на данный момент состоит в том, чтобы выделить больше места (память — это не проблема, код привязан к процессору).:

 class objR{
 public:
   objR(const size_t N){
     int nth=1;
     #ifdef _OPENMP
       nth=omp_get_max_threads();
     #endif
     _buffer.resize(N);
   }

   double operator(...) const{
     int thread_id=0;
     #ifdef _OPENMP
       thread_id = omp_get_thread_num();
     #endif
     // do something, using the _buffer[thread_id] to store intermediaries
   }

 private:
   std::vector< std::vector<double> > _buffer;
};
  

Кажется, это работает правильно. Тем не менее, поскольку это мой первый опыт работы с многопоточностью, я был бы признателен, если бы кто-нибудь знающий мог прокомментировать, есть ли лучший подход.

Комментарии:

1. Я думаю, вам, возможно, потребуется предоставить минимальный компилируемый пример, чтобы продемонстрировать проблему…

2. Какой компилятор? Какая версия? ОТ: Если вы используете g -fopenmp -D_GLIBCXX_PARALLEL , вы должны получить результаты, которые, как известно, хорошо работают, по крайней мере, если исправление этой ошибки уже дошло до вашего дистрибутива 🙂

3. @sehe: g (Ubuntu 4.4.3-4ubuntu5) 4.4.3 на lucid, скомпилирован с помощью g -O3 -fopenmp; ваше предложение определенно интересно, но не устраняет проблему. И. вы предложили мне запустить valgrind на нем; он не жалуется, но результаты valgrind ./a.out являются правильными (в то время как just ./a.out выдает мусор). Что, черт возьми, это может быть тогда

4. Очевидно, что замена подпрограмм malloc / free скрывает проблему в вашей конкретной программе. Бьюсь об заклад, если вы предварительно загрузите tcmalloc ( LD_PRELOAD="/usr/lib/libtcmalloc.so" ) , это тоже скроет проблему. У меня нет ответа. У меня тоже нет кода, поэтому я уверен, что так и останется

5. Нам нужно больше информации о _S и R. Проблемы могут быть вызваны перегруженным оператором, но без дополнительной информации мы бы просто гадали.

Ответ №1:

Доступ к _S[i].first и _S[i].second совершенно безопасен (не могу ничего гарантировать о atoms ). Это означает, что причиной проблемы должен быть вызов вашей функции R . Вам нужно выяснить, что R такое, и опубликовать, что он делает.

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