即使在num_threads(1)时使用openmp也难以理解性能提升

时间:2015-06-04 23:40:57

标签: c++ openmp

以下代码行

int nrows = 4096;
int ncols = 4096;
size_t numel = nrows * ncols;
unsigned char *buff = (unsigned char *) malloc( numel );

unsigned char *pbuff = buff;
#pragma omp parallel for schedule(static), firstprivate(pbuff, nrows, ncols), num_threads(1)
for (int i=0; i<nrows; i++)
{
    for (int j=0; j<ncols; j++)
    {
        *pbuff += 1;
        pbuff++;
    }
}

使用

编译时,在我的i5-3230M上运行11130 usecs
g++ -o main main.cpp -std=c++0x -O3

也就是说,当忽略openmp pragma时。

另一方面,使用

编译时只需要1496个usecs
g++ -o main main.cpp -std=c++0x -O3 -fopenmp

这速度提高了6倍以上,考虑到它是在2核机器上运行,这是非常令人惊讶的。事实上,我还使用 num_threads(1)对其进行了测试,性能提升仍然非常重要(速度提高了3倍以上)。

任何人都可以帮我理解这种行为吗?

编辑:按照建议,我提供完整的代码:

#include <stdlib.h>
#include <iostream>

#include <chrono>
#include <cassert>


int nrows = 4096;
int ncols = 4096;
size_t numel = nrows * ncols;
unsigned char * buff;


void func()
{
    unsigned char *pbuff = buff;
    #pragma omp parallel for schedule(static), firstprivate(pbuff, nrows, ncols), num_threads(1)
    for (int i=0; i<nrows; i++)
    {
        for (int j=0; j<ncols; j++)
        {
            *pbuff += 1;
            pbuff++;
        }
    }
}


int main()
{
    // alloc & initializacion
    buff = (unsigned char *) malloc( numel );
    assert(buff != NULL);
    for(int k=0; k<numel; k++)
        buff[k] = 0;

    //
    std::chrono::high_resolution_clock::time_point begin;
    std::chrono::high_resolution_clock::time_point end;
    begin = std::chrono::high_resolution_clock::now();      
    //
    for(int k=0; k<100; k++)
        func();
    //
    end = std::chrono::high_resolution_clock::now();
    auto usec = std::chrono::duration_cast<std::chrono::microseconds>(end-begin).count();
    std::cout << "func average running time: " << usec/100 << " usecs" << std::endl;

    return 0;
}

1 个答案:

答案 0 :(得分:5)

事实证明,答案是firstprivate(pbuff, nrows, ncols)有效地将pbuffnrowsncols声明为for循环范围内的局部变量。这反过来意味着编译器可以将nrowsncols视为常量 - 它不能对全局变量做出相同的假设!

因此,使用-fopenmp,您最终会获得巨大的加速,因为您每次迭代都不会访问全局变量。 (另外,如果值为ncols,则编译器会进行一些循环展开)。

通过更改

int nrows = 4096;
int ncols = 4096;

const int nrows = 4096;
const int ncols = 4096;
通过更改

for (int i=0; i<nrows; i++)
{
    for (int j=0; j<ncols; j++)
    {
        *pbuff += 1;
        pbuff++;
    }
}

int _nrows = nrows;
int _ncols = ncols;
for (int i=0; i<_nrows; i++)
{
    for (int j=0; j<_ncols; j++)
    {
        *pbuff += 1;
        pbuff++;
    }
}

异常加速消失 - 非OpenMP代码现在和OpenMP代码一样快。

故事的寓意?避免在性能关键循环中访问可变全局变量。