На прошлой неделе я писал raytracer, и дошло до того, что этого достаточно, чтобы многопоточность имела смысл.Я пытался использовать OpenMP для его распараллеливания, но запуск с большим количеством потоков на самом деле медленнее, чем с одним.
При чтении других похожих вопросов, особенно об OpenMP, было высказано предположение, что gcc оптимизирует последовательный код лучше.Однако выполнение скомпилированного кода ниже с export OMP_NUM_THREADS=1
в два раза быстрее, чем с export OMP_NUM_THREADS=4
.Т.е. это одинаковый скомпилированный код на обоих запусках.
Запуск программы с time
:
> export OMP_NUM_THREADS=1; time ./raytracer
real 0m34.344s
user 0m34.310s
sys 0m0.008s
> export OMP_NUM_THREADS=4; time ./raytracer
real 0m53.189s
user 0m20.677s
sys 0m0.096s
Время пользователя намного меньше реального , чтонеобычно при использовании нескольких ядер - пользователь должен быть больше, чем real , так как несколько ядер работают одновременно.
Код, который я распараллелил с использованием OpenMP
void Raytracer::render( Camera& cam ) {
// let the camera know to use this raytracer for probing the scene
cam.setSamplingFunc(getSamplingFunction());
int i, j;
#pragma omp parallel private(i, j)
{
// Construct a ray for each pixel.
#pragma omp for schedule(dynamic, 4)
for (i = 0; i < cam.height(); ++i) {
for (j = 0; j < cam.width(); ++j) {
cam.computePixel(i, j);
}
}
}
}
Читая этот вопрос Я думал, что нашел свой ответ.В нем рассказывается о реализации gclib rand (), синхронизирующей вызовы с самим собой, чтобы сохранить состояние для генерации случайных чисел между потоками.Я часто использую rand () для выборки Монте-Карло, поэтому я подумал, что это проблема.Я избавился от вызовов rand, заменив их одним значением, но использование нескольких потоков все еще медленнее. РЕДАКТИРОВАТЬ: упс оказывается, я не проверял это правильно, это были случайные значения!
Теперь, когда это не так, я буду обсуждать обзор того, что делается накаждый вызов computePixel
, так что, надеюсь, решение будет найдено.
В моем raytracer у меня по существу есть дерево сцены со всеми объектами в нем.Это дерево часто пересекается в течение computePixel
, когда объекты проверяются на пересечение, однако запись в это дерево или любые объекты не производится.computePixel
, по сути, читает сцену несколько раз, вызывая методы для объектов (все из которых являются const-методами), и в самом конце записывает одно значение в свой собственный массив пикселей.Это единственная часть, о которой мне известно, когда более чем один поток попытается записать в одну переменную-член.Синхронизация нигде не существует, поскольку два потока не могут записывать в одну и ту же ячейку в массиве пикселей.
Кто-нибудь может предложить места, где могут быть какие-то разногласия?Что попробовать?
Заранее спасибо.
РЕДАКТИРОВАТЬ: Извините, глупо не предоставлять дополнительную информацию о моей системе.
- Компилятор gcc 4.6 (с оптимизацией -O2)
- Ubuntu Linux 11.10
- OpenMP 3
- Четырехъядерный процессор Intel i3-2310M 2,1 ГГц (на моем ноутбуке на данный момент)
Код для вычисления пикселя:
class Camera {
// constructors destructors
private:
// this is the array that is being written to, but not read from.
Colour* _sensor; // allocated using new at construction.
}
void Camera::computePixel(int i, int j) const {
Colour col;
// simple code to construct appropriate ray for the pixel
Ray3D ray(/* params */);
col += _sceneSamplingFunc(ray); // calls a const method that traverses scene.
_sensor[i*_scrWidth+j] += col;
}
Исходя из предположений, это может быть обход дерева, который вызывает замедление.Некоторые другие аспекты: при вызове функции выборки (рекурсивное отражение лучей) возникает довольно много рекурсии - может ли это вызвать эти проблемы?