OpenMP を使用してマルチプロセッシングを利用するために、教育目的で少し前に書いたレイトレーサーを変更しました。ただし、並列化による利益は見られません。
私は 3 つの異なるアプローチを試しました。タスク プール環境 (draw_pooled()
関数)、イメージの行レベルの並列処理を使用した標準の OMP 並列ネストfor
ループ ( )、およびピクセル レベルの並列処理を使用したdraw_parallel_for()
別の OMP 並列処理 ( ) です。オリジナルのシリアル描画ルーチンも参照用に含まれています ( )。for
draw_parallel_for2()
draw_serial()
Intel Core 2 Duo E6750 (2 コア @ 2,67 GHz それぞれハイパースレッディング付き) で 2560x1920 のレンダリングを実行しており、Linux では 4 GB の RAM を使用し、libgomp を使用して gcc でバイナリをコンパイルしています。シーンの平均は次のとおりです。
- 一連のレンダリングに 120 秒、
- しかし、上記の 3 つの特定の方法のどれを選択しても、2 つのスレッド (デフォルト - CPU コアの数) で並行してこれを行うには196 秒 ( sic! ) かかります。
- HT を考慮して OMP のデフォルトのスレッド番号を 4 にオーバーライドすると、並列レンダリング時間は 177 秒に短縮されます。
なぜこうなった?並列コードに明らかなボトルネックは見当たりません。
編集:明確にするために-タスクプールは実装の1つにすぎません。質問を読んでください-下にスクロールして、並列を表示しfor
ます。問題は、タスクプールと同じくらい遅いということです!
void draw_parallel_for(int w, int h, const char *fname) {
unsigned char *buf;
buf = new unsigned char[w * h * 3];
Scene::GetInstance().PrepareRender(w, h);
for (int y = 0; y < h; ++y) {
#pragma omp parallel for num_threads(4)
for (int x = 0; x < w; ++x)
Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
}
write_png(buf, w, h, fname);
delete [] buf;
}
void draw_parallel_for2(int w, int h, const char *fname) {
unsigned char *buf;
buf = new unsigned char[w * h * 3];
Scene::GetInstance().PrepareRender(w, h);
int x, y;
#pragma omp parallel for private(x, y) num_threads(4)
for (int xy = 0; xy < w * h; ++xy) {
x = xy % w;
y = xy / w;
Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
}
write_png(buf, w, h, fname);
delete [] buf;
}
void draw_parallel_for3(int w, int h, const char *fname) {
unsigned char *buf;
buf = new unsigned char[w * h * 3];
Scene::GetInstance().PrepareRender(w, h);
#pragma omp parallel for num_threads(4)
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x)
Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
}
write_png(buf, w, h, fname);
delete [] buf;
}
void draw_serial(int w, int h, const char *fname) {
unsigned char *buf;
buf = new unsigned char[w * h * 3];
Scene::GetInstance().PrepareRender(w, h);
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x)
Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
}
write_png(buf, w, h, fname);
delete [] buf;
}
std::queue< std::pair<int, int> * > task_queue;
void draw_pooled(int w, int h, const char *fname) {
unsigned char *buf;
buf = new unsigned char[w * h * 3];
Scene::GetInstance().PrepareRender(w, h);
bool tasks_issued = false;
#pragma omp parallel shared(buf, tasks_issued, w, h) num_threads(4)
{
#pragma omp master
{
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x)
task_queue.push(new std::pair<int, int>(x, y));
}
tasks_issued = true;
}
while (true) {
std::pair<int, int> *coords;
#pragma omp critical(task_fetch)
{
if (task_queue.size() > 0) {
coords = task_queue.front();
task_queue.pop();
} else
coords = NULL;
}
if (coords != NULL) {
Scene::GetInstance().RenderPixel(coords->first, coords->second,
buf + (coords->second * w + coords->first) * 3);
delete coords;
} else {
#pragma omp flush(tasks_issued)
if (tasks_issued)
break;
}
}
}
write_png(buf, w, h, fname);
delete [] buf;
}