#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
такое, и опубликовать, что он делает.
В другом пункте имена, которые начинаются с подчеркивания и начинаются с символа верхнего регистра, зарезервированы для реализации, и вы вызываете неопределенное поведение, используя их.