在针对顺序运行进行优化的程序上使用 openMP 后没有性能提升

Posted

技术标签:

【中文标题】在针对顺序运行进行优化的程序上使用 openMP 后没有性能提升【英文标题】:No performance gain after using openMP on a program optimize for sequential running 【发布时间】:2012-05-30 11:13:58 【问题描述】:

我已尽我所能优化我的顺序运行功能。 当我使用 openMP 时,我发现性能没有任何提升。 我在1核机器和8核机器上试了我的程序,性能是一样的。 将年份设置为 20,我有 1 个核心:1 秒。 8 核:1 秒。

将年份设置为 25 我有 1 个核心:40 秒。 8 核:40 秒。

1 核机器:我的笔记本电脑的 intel core 2 duo 1.8 GHz,ubuntu linux 8核机:3.25GHz,ubuntu linux

我的程序枚举了二叉树的所有可能路径,并在每条路径上做一些工作。所以我的循环大小呈指数增长,我希望 openMP 线程的占用空间为零。在我的循环中,我只减少一个变量。所有其他变量都是只读的。我只使用我写的函数,我认为它们是线程安全的。

我还在我的程序上运行 Valgrind cachegrind。我不完全理解输出,但似乎没有缓存未命中或错误共享。

我用

编译
gcc -O3 -g3 -Wall -c -fmessage-length=0 -lm -fopenmp -ffast-math

我的完整程序如下。很抱歉发布了很多代码。我对 openMP 和 C 都不熟悉,而且我无法在不丢失主要任务的情况下恢复我的代码。

如何在使用 openMP 时提高性能? 它们是使程序运行得更快的编译器标志或 C 技巧吗?

test.c

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <omp.h>
#include "test.h"

int main()

    printf("starting\n");
    int year=20;
    int tradingdate0=1;

    globalinit(year,tradingdate0);

    int i;
    float v=0;
    long n=pow(tradingdate0+1,year);
    #pragma omp parallel for reduction(+:v)
    for(i=0;i<n;i++)
        v+=pathvalue(i);

    globaldel();
    printf("finished\n");
    return 0;


//***function on which openMP is applied
float pathvalue(long pathindex) 
    float value = -ctx.firstpremium;
    float personalaccount = ctx.personalaccountat0;
    float account = ctx.firstpremium;
    int i;
    for (i = 0; i < ctx.year-1; i++) 
        value *= ctx.accumulationfactor;
        double index = getindex(i,pathindex);
        account = account * index;
        double death = fmaxf(account,ctx.guarantee[i]);
        value += qx(i) * death;
        if (haswithdraw(i))
            double withdraw = personalaccount*ctx.allowed;
            value += px(i) * withdraw;
            personalaccount = fmaxf(personalaccount-withdraw,0);
            account = fmaxf(account-withdraw,0);
        
    

    //last year
    double index = getindex(ctx.year-1,pathindex);
    account = account * index;
    value+=fmaxf(account,ctx.guarantee[ctx.year-1]);

    return value * ctx.discountfactor;




int haswithdraw(int period)
    return 1;


float getindex(int period, long pathindex)
    int ndx = (pathindex/ctx.chunksize[period])%ctx.tradingdate;
    return ctx.stock[ndx];


float qx(int period)
    return 0;


float px(int period)
    return 1;


//****global
struct context ctx;

void globalinit(int year, int tradingdate0)
    ctx.year = year;
    ctx.tradingdate0 = tradingdate0;
    ctx.firstpremium = 1;
    ctx.riskfreerate = 0.06;
    ctx.volatility=0.25;
    ctx.personalaccountat0 = 1;
    ctx.allowed = 0.07;
    ctx.guaranteerate = 0.03;
    ctx.alpha=1;
    ctx.beta = 1;
    ctx.tradingdate=tradingdate0+1;
    ctx.discountfactor = exp(-ctx.riskfreerate * ctx.year);
    ctx.accumulationfactor = exp(ctx.riskfreerate);
    ctx.guaranteefactor = 1+ctx.guaranteerate;
    ctx.upmove=exp(ctx.volatility/sqrt(ctx.tradingdate0));
    ctx.downmove=1/ctx.upmove;

    ctx.stock=(float*)malloc(sizeof(float)*ctx.tradingdate);
    int i;
    for(i=0;i<ctx.tradingdate;i++)
        ctx.stock[i]=pow(ctx.upmove,ctx.tradingdate0-i)*pow(ctx.downmove,i);

    ctx.chunksize=(long*)malloc(sizeof(long)*ctx.year);
    for(i=0;i<year;i++)
        ctx.chunksize[i]=pow(ctx.tradingdate,ctx.year-i-1);

    ctx.guarantee=(float*)malloc(sizeof(float)*ctx.year);
    for(i=0;i<ctx.year;i++)
        ctx.guarantee[i]=ctx.beta*pow(ctx.guaranteefactor,i+1);


void globaldel()
    free(ctx.stock);
    free(ctx.chunksize);
    free(ctx.guarantee);

测试.h

float pathvalue(long pathindex);
int haswithdraw(int period);
float getindex(int period, long pathindex);
float qx(int period);
float px(int period);
//***global
struct context
    int year;
    int tradingdate0;
    float firstpremium;
    float riskfreerate;
    float volatility;
    float personalaccountat0;
    float allowed;
    float guaranteerate;
    float alpha;
    float beta;
    int tradingdate;
    float discountfactor;
    float accumulationfactor;
    float guaranteefactor;
    float upmove;
    float downmove;
    float* stock;
    long* chunksize;
    float* guarantee;
;
struct context ctx;
void globalinit();
void globaldel();

EDIT 我将所有全局变量简化为常量。 20 年来,程序运行速度快了两倍(太棒了!)。例如,我尝试使用OMP_NUM_THREADS=4 ./test 设置线程数。但这并没有给我带来任何性能提升。 我的 gcc 会不会有问题?

test.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <omp.h>
#include "test.h"


int main()

    starttimer();
    printf("starting\n");
    int i;
    float v=0;

    #pragma omp parallel for reduction(+:v)
    for(i=0;i<numberofpath;i++)
        v+=pathvalue(i);

    printf("v:%f\nfinished\n",v);
    endtimer();
    return 0;


//function on which openMP is applied
float pathvalue(long pathindex) 
    float value = -firstpremium;
    float personalaccount = personalaccountat0;
    float account = firstpremium;
    int i;
    for (i = 0; i < year-1; i++) 
        value *= accumulationfactor;
        double index = getindex(i,pathindex);
        account = account * index;
        double death = fmaxf(account,guarantee[i]);
        value += death;
        double withdraw = personalaccount*allowed;
        value += withdraw;
        personalaccount = fmaxf(personalaccount-withdraw,0);
        account = fmaxf(account-withdraw,0);
    

    //last year
    double index = getindex(year-1,pathindex);
    account = account * index;
    value+=fmaxf(account,guarantee[year-1]);

    return value * discountfactor;




float getindex(int period, long pathindex)
    int ndx = (pathindex/chunksize[period])%tradingdate;
    return stock[ndx];


//timing
clock_t begin;

void starttimer()
    begin = clock();


void endtimer()
    clock_t end = clock();
    double elapsed = (double)(end - begin) / CLOCKS_PER_SEC;
    printf("\nelapsed: %f\n",elapsed);

测试.h

float pathvalue(long pathindex);
int haswithdraw(int period);
float getindex(int period, long pathindex);
float qx(int period);
float px(int period);
//timing
void starttimer();
void endtimer();
//***constant
const int year= 20 ;
const int tradingdate0= 1 ;
const float firstpremium= 1 ;
const float riskfreerate= 0.06 ;
const float volatility= 0.25 ;
const float personalaccountat0= 1 ;
const float allowed= 0.07 ;
const float guaranteerate= 0.03 ;
const float alpha= 1 ;
const float beta= 1 ;
const int tradingdate= 2 ;
const int numberofpath= 1048576 ;
const float discountfactor= 0.301194211912 ;
const float accumulationfactor= 1.06183654655 ;
const float guaranteefactor= 1.03 ;
const float upmove= 1.28402541669 ;
const float downmove= 0.778800783071 ;
const float stock[2]=1.2840254166877414, 0.7788007830714049;
const long chunksize[20]=524288, 262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1;
const float guarantee[20]=1.03, 1.0609, 1.092727, 1.1255088100000001, 1.1592740743, 1.1940522965290001, 1.2298738654248702, 1.2667700813876164, 1.304773183829245, 1.3439163793441222, 1.384233870724446, 1.4257608868461793, 1.4685337134515648, 1.512589724855112, 1.557967416600765, 1.6047064390987882, 1.6528476322717518, 1.7024330612399046, 1.7535060530771016, 1.8061112346694148;

【问题讨论】:

顺序代码已经获得了性能,你应该总是从这个开始。带有参数的全局结构基本上会扼杀编译器优化的所有可能性。规则很简单,将所有常量作为常量(enum 用于整数或 #define 用于浮点)并将所有运行时参数作为参数传递给您的函数。你这样做的方式,编译器不能确定程序的其他部分不会改变struct的特定值,所以它不能进行持续传播。清理它也将有助于并行编译。 @JensGustedt 感谢您告诉我们管理全局变量的正确方法。它使我的代码速度提高了 2 倍(请参阅我在问题中的编辑)。不过,我仍然认为并行化没有任何好处。 尼古拉斯,虽然你没有直接关注它。使用您的方法,一旦您有一个包含多个 .o 文件的程序,您将很难使用多个定义的符号。如果是您的 gcc 有问题,我们无法判断,您甚至没有告诉我们您使用的是哪个版本。要查看 OpenMP 是否有所作为,请将您的程序编译为程序集(使用 -O3 -S)并比较使用和不使用 -fopenmp 的结果代码。 @JensGustedt 我认为多定义问题可以通过使用extern 声明来解决。否则,你能勾勒出正确的方法吗?我的 gcc 终于没问题了,正如 Hristo Iliev 所指出的那样,我没有正确测量 openMP 性能。 一个声明(如果你有extern)不能有初始化。所以你的一些代码看不到价值,优化潜力会小很多。 【参考方案1】:

即使您的程序受益于使用 OpenMP,您也不会看到它,因为您测量了错误的时间。

clock() 返回在所有线程中花费的总 CPU 时间。如果您使用四个线程运行并且每个线程运行 1/4 的时间,clock() 仍将返回相同的值,因为 4*(1/4) = 1。您应该测量 挂钟时间 代替。

将对clock() 的调用替换为omp_get_wtime()gettimeofday()。它们都提供高精度的挂钟计时。

附注为什么SO周围有这么多人用clock()计时?

【讨论】:

非常好的洞察力。那正是我的问题。正确测量时间时,我发现我的 1 核和 8 核机器之间的速度提高了 7 倍。谢谢你。就我而言,使用clock() 是由于新手。【参考方案2】:

我没有看到您指定 OpenMP 将使用的内核数量的任何部分。默认情况下,它应该使用它看到的 CPU 数量,但出于我的目的,我总是强制它使用我指定的数量。

在您的并行构造之前添加此行:

#pragma omp parallel num_threads(num_threads)

   // Your parallel for follows here

...其中num_threads 是介于 1 和您机器上的内核数之间的整数。

编辑:这是用于构建代码的生成文件。将其放在同一目录中名为 Makefile 的文本文件中。

test: test.c test.h
    cc -o $@ $< -O3 -g3 -fmessage-length=0 -lm -fopenmp -ffast-math

【讨论】:

Makoto,IMO 这不是 Nicolas 没有看到加速的原因(除非他的机器是单核的)。 @AaterSuleman:在某个地方处理 OpenMP 时,您确实需要指定线程数 - 不管是全局变量还是通过这个变量。 就像您指出的那样,除非另有说明,否则它将其设置为可用内核的数量。因此,在他的 8 核系统上,即使他没有指定任何线程,也会有 8 个(如果是 HT,则为 16 个)线程。 @Makoto 我尝试了你的方法,但我的 8 核机器上仍然没有性能提升。 @NicolasEssis-Breton:哪台电脑?由于切换线程,单核将具有更高的开销。另外,我确实想指出,代码(没有 -c 标志)实际上在我的四核机器上按原样运行非常快【参考方案3】:

似乎它应该工作。可能您需要指定要使用的线程数。您可以通过设置 OMP_NUM_THREADS 变量来做到这一点。例如,使用 4 个线程:

OMP_NUM_THREADS=4 ./test

编辑:我刚刚编译了代码,并且在更改线程数时观察到显着的加速。

【讨论】:

我试过你的方法,但是我的 1 核和 8 核机器的性能是一样的。你能发布你的 gcc 命令吗? @NicolasEssis-Breton 我使用了与您发布的完全相同的命令行。唯一的区别是我将 year 增加到 22(year=20,程序完成得如此之快,以至于无法测量任何加速)。对于 year=22,从 1 到 4 个线程(我的机器有 4 个内核)有 2 倍的加速。这不是线性加速,但绝对是显着的。

以上是关于在针对顺序运行进行优化的程序上使用 openMP 后没有性能提升的主要内容,如果未能解决你的问题,请参考以下文章

OpenMP:使用较少的长线程更好,还是使用最大可用的短线程更好?

使用 OPENMP 进行并行归并排序

如何在 Python 中进行并行编程?

不同主机上的 OpenMP 程序

C ++ NUMA优化

OpenMP:“隐式屏障”中有 20% 的时间?