繁体   English   中英

并行化导致的性能损失

[英]Performance loss from parallelization

我出于教育目的修改了我不久前写的raytracer,以利用OpenMP进行多处理。 但是,我看不到并行化有任何好处。

我尝试了3种不同的方法:一个任务池环境( draw_pooled()函数),一个标准的OMP并行嵌套循环, for图像行级并行( draw_parallel_for() ),以及另一个OMP并行, for像素级并行度( draw_parallel_for2() )。 还包含原始的串行绘图例程,以供参考( draw_serial() )。

我正在Linux Core下在Intel Core 2 Duo E6750(2个内核,每个@ 2,67GHz,具有超线程)上运行2560x1920渲染,并在Linux下使用4GB RAM,由gcc用libgomp编译。 该场景平均需要:

  • 120秒连续渲染
  • 但无论我选择上述三种特定方法中的哪一种,都可以在2个线程(默认值-CPU内核数)中并行执行196秒( sic! ),
  • 如果我将OMP的默认线程号替换为4以考虑HT,则并行渲染时间将降至177秒。

为什么会这样呢? 我在并行代码中看不到任何明显的瓶颈。

编辑:只是为了澄清-任务池仅仅是实现的一个 ,请阅读问题-向下滚动才能看到平行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;
}

您的最内层循环中有一个关键部分。 换句话说,您要达到每个像素一个同步基 这会降低性能。

最好将场景分割为多个图块,并在每个线程上使用一个。 这样,您之间的同步时间就更长(整个图块的处理价值)。

如果像素是独立的,则实际上不需要任何锁定。 您可以将图像分为行或列,然后让线程自己工作。 例如,您可以让每个线程在第n行上运行(伪代码):

for(int y = TREAD_NUM; y < h; y += THREAD_COUNT)
    for(int x = 0; x < w; ++x)
        render_pixel(x,y);

其中THREAD_NUM是每个线程的唯一编号,例如0 <= THREAD_NUM < THREAD_COUNT 然后, 您加入线程池之后,执行png转换。

创建线程时始终会有性能开销。 在for循环内的OMP并行显然会产生大量开销。 例如,在您的代码中

void draw_parallel_for(int w, int h, const char *fname) {

    for (int y = 0; y < h; ++y) {

    // Here There is a lot of overhead
         #pragma omp parallel for num_threads(4)
         for (int x = 0; x < w; ++x)
              Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }
 }

可以重写为

void draw_parallel_for(int w, int h, const char *fname) {


    #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);
    }
 }

要么

void draw_parallel_for(int w, int h, const char *fname) {


    #pragma omp parallel num_threads(4)
    for (int y = 0; y < h; ++y) {
           #pragma omp for
           for (int x = 0; x < w; ++x)
              Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }
 }

这样,您将消除开销

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM