【问题标题】:optimize MSE algorithm using openmp使用 openmp 优化 MSE 算法
【发布时间】:2013-02-11 01:51:25
【问题描述】:

我想使用 openMP 优化下面的代码

double val;
double m_y = 0.0f;
double m_u = 0.0f;
double m_v = 0.0f;

#define _MSE(m, t) \
val = refData[t] - calData[t];  \
m += val*val; 

#pragma omp parallel 
 {
 #pragma omp for
for( i=0; i<(width*height)/2; i++ ) {  //yuv422: 2 pixels at a time
    _MSE(m_u, 0); 
    _MSE(m_y, 1); 
    _MSE(m_v, 2); 
    _MSE(m_y, 3); 

  #pragma omp reduction(+:refData) reduction(+:calData)
    refData += 4;
    calData += 4;
 // int id = omp_get_thread_num();
 //printf("Thread %d performed %d iterations of the loop\n",id ,i);
}

}

欢迎任何关于优化上述代码的建议,目前我有错误的输出。

【问题讨论】:

    标签: c algorithm optimization openmp mse


    【解决方案1】:

    我认为您可以做的最简单的事情是让它分成 4 个线程,并计算每个线程中的 UYVY 错误。与其让它们分开值,不如让它们成为一个数组:

    double sqError[4] = {0};
    const int numBytes = width * height * 2;
    
    #pragma omp parallel for
    for( int elem = 0; elem < 4; elem++ ) {
        for( int i = elem; i < numBytes; i += 4 ) {
            int val = refData[i] - calData[i];
            sqError[elem] += (double)(val*val);
        }
    }
    

    这样,每个线程只在一个事物上运行,不会发生争用。

    也许它不是 OMP 的最高级用法,但您应该会看到加速。


    在您对性能影响发表评论后,我做了一些实验,发现性能确实更差。我怀疑这可能是由于缓存未命中。

    你说:

    这次使用 openMP 达到了性能:时间:0.040637 使用串行 时间:0.018670

    所以我使用减少每个变量并使用单个循环对其进行了重新设计:

        #pragma omp parallel for reduction(+:e0) reduction(+:e1) reduction(+:e2) reduction(+:e3)
        for( int i = 0; i < numBytes; i += 4 ) {
            int val = refData[i] - calData[i];
            e0 += (double)(val*val);
            val = refData[i+1] - calData[i+1];
            e1 += (double)(val*val);
            val = refData[i+2] - calData[i+2];
            e2 += (double)(val*val);
            val = refData[i+3] - calData[i+3];
            e3 += (double)(val*val);
        }
    

    在 4 核机器上使用我的测试用例,我观察到不到 4 倍的改进:

    serial:             2025 ms
    omp with 2 loops:   6850 ms
    omp with reduction: 455  ms
    

    [编辑]关于为什么第一段代码的性能比非并行版本差,Hristo Iliev说:

    您的第一段代码是虚假共享的可怕示例 在多线程代码中执行。由于 sqError 只有 8 个字节的 4 个元素 每个,它都适合单个缓存行(即使在半个缓存行中) 现代 x86 CPU)。有 4 个线程不断向相邻线程写入 元素,这将产生大量的内核间缓存 由于虚假共享而失效。可以通过使用来解决这个问题 而是像这样的结构 struct _error { double val;双倍的 垫[7]; } sqError[4];现在每个 sqError[i].val 将在一个单独的 缓存行,因此没有虚假共享。

    【讨论】:

    • 谢谢你,我很快做出这些改变并测试了,这消除了数据依赖并提高了性能。但是计算出的并行 openmp 输出与串行程序输出不同。
    • 你是说注释掉#pragma这一行,结果就不一样了?还是您将其与不同的程序进行比较?由于这是 C,您可能在循环外定义 i。如果是这种情况,请尝试将子句 private(i) 添加到 #pragma 行的末尾。与val 相同。
    • 作为参考,这些是我所做的更改: const int nBytes = (widthheight)/2;双 sqError[4] = {0}; #pragma omp 并行 { #pragma omp for for(int elem = 0 ; elem val); } } } mse_y = ((double)(sqError[1] + sqError[3] )/ ( 宽度 * 高度 )); mse_u = ((double)sqError[0] / ( 宽度 * 高度 / 2 )); mse_v = ((double)sqError[2] / ( 宽度 * 高度 / 2 ));
    • 对,所以你误读了我的回答。 nBytes 不是 (width*height)/2。请注意,我以 4 步递增 i
    • 你的第一段代码是多线程代码中虚假共享的一个可怕例子。由于sqError 只有 4 个元素,每个元素 8 字节,它适合单个高速缓存行(即使在现代 x86 CPU 上的半个高速缓存行中)。由于 4 个线程不断写入相邻元素,这会由于错误共享而产生大量的内核间缓存失效。可以通过使用类似struct _error { double val; double pad[7]; } sqError[4]; 这样的结构来解决此问题。现在每个sqError[i].val 将位于单独的缓存行中,因此不会出现错误共享。
    【解决方案2】:

    代码看起来像是在计算 MSE,但添加到相同的总和 m。为了使并行性正常工作,您需要消除m 的共享,一种方法是预先分配一个数组(我想是宽度*高度/2)来存储不同的总和,或者ms。最后,把所有的总和放在最后。

    另外,测试一下这实际上更快!

    【讨论】:

    • 感谢 Cong 提供我们尝试过的输入,但它达到了性能。正在寻找其他版本。
    猜你喜欢
    • 2013-10-05
    • 1970-01-01
    • 1970-01-01
    • 2021-09-20
    • 2021-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多