仅在将参数传递给程序时使用 openMP

Posted

技术标签:

【中文标题】仅在将参数传递给程序时使用 openMP【英文标题】:Use openMP only when an argument is passed to the program 【发布时间】:2018-05-20 01:32:22 【问题描述】:

只有在将-omp 参数传递给程序的情况下,是否有使用 OpenMP 并行化 for 循环的好方法?

这似乎是不可能的,因为#pragma omp parallel for 是一个预处理器指令,因此甚至在编译时间之前就进行了评估,当然只有在运行时将参数传递给程序才能确定。

目前我正在使用一个非常丑陋的解决方案来实现这一点,这会导致大量的代码重复。

if(ompDefined) 
#pragma omp parallel for
  for(...)
    ...

else 
  for(...)
    ...

【问题讨论】:

这个***.com/questions/4085595/conditional-pragma-omp 可能很有趣。 【参考方案1】:

我认为您正在寻找的可以使用CPU dispatcher technique 来解决。

为了对 OpenMP 代码与非 OpenMP 代码进行基准测试,您可以像这样从相同的源代码创建不同的目标文件

//foo.c
#ifdef _OPENMP
double foo_omp() 
#else
double foo() 
#endif
  double sum = 0;
  #pragma omp parallel for reduction(+:sum)
  for(int i=0; i<1000000000; i++) sum += i%10;
  return sum;

这样编译

gcc -O3 -c foo.c
gcc -O3 -fopenmp -c foo.c -o foo_omp.o

这将创建两个目标文件foo.ofoo_omp.o。然后你可以像这样调用这些函数之一

//bar.c
#include <stdio.h>

double foo();
double foo_omp();
double (*fp)();

int main(int argc, char *argv[]) 
  if(argc>1) 
    fp = foo_omp;
  
  else 
    fp = foo;
  
  double sum = fp();
  printf("sum %e\n", sum);

像这样编译和链接

gcc -O3 -fopenmp bar.c foo.o foo_omp.o

然后我像这样对代码计时

time ./a.out -omp
time ./a.out

在我的 4 核/8 个硬件线程的系统上,第一种情况大约需要 0.4 秒,第二种情况大约需要 1.2 秒。


这是一个只需要一个源文件的解决方案

#include <stdio.h>

typedef double foo_type();

foo_type foo, foo_omp, *fp;

#ifdef _OPENMP
#define FUNCNAME foo_omp
#else
#define FUNCNAME foo
#endif

double FUNCNAME () 
  double sum = 0;
  #pragma omp parallel for reduction(+:sum)
  for(int i=0; i<1000000000; i++) sum += i%10;
  return sum;


#ifdef _OPENMP
int main(int argc, char *argv[]) 
  if(argc>1) 
    fp = foo_omp;
  
  else 
    fp = foo;
  
  double sum = fp();
  printf("sum %e\n", sum);

#endif

这样编译

gcc -O3 -c foo.c
gcc -O3 -fopenmp foo.c foo.o

【讨论】:

【参考方案2】:

你可以在运行时通过callingomp_set_num_threads设置线程数:

#include <omp.h>

int main() 

    int threads = 1;

    #ifdef _OPENMP
    omp_set_num_threads(threads);
    #endif

    #pragma omp parallel for
    for(...) 
    
        ...
    

这与禁用 OpenMP 不太一样,但它会阻止它并行运行计算。我发现使用命令行开关设置它总是一个好主意(您可以使用 GNU getopt 或 Boost.ProgramOptions 来实现它)。这使您可以轻松地在同一代码上运行单线程和多线程测试。

正如 Vladimir F 在 cmets 中指出的那样,您还可以在执行程序之前通过设置环境变量 OMP_NUM_THREADS 来设置线程数:

gcc -Wall -Werror -pedantic -O3 -fopenmp -o test test.c 
OMP_NUM_THREADS=1
./test
unset OMP_NUM_THREADS

最后,您可以在编译时禁用 OpenMP,方法是不为 GCC 提供 -fopenmp 选项。但是,您需要在代码中需要启用 OpenMP 的任何行周围放置预处理器保护(见上文)。如果您想使用 OpenMP 库中包含的某些函数而不实际启用 OpenMP 编译指示,您只需将 -fopenmp 选项替换为 -lgomp 即可链接到 OpenMP 库。

【讨论】:

谢谢,这是一个比我目前拥有的更好的解决方案。尽管如此,我正在做一些基准测试,并且通过使用 OpenMP 的一个线程运行代码并在不使用 OpenMP 的情况下运行代码对速度产生了相当大的影响OpenMP .... 不幸的是,这会给我带来稍微错误的结果。 你不能不向编译器提供 OpenMP 开关吗?自从我使用 OpenMP 已经有一段时间了,但我认为 GCC 仅在您将 -fopenmp 选项传递给它时才启用 OpenMP。 我认为你需要用_OPENMP预处理宏来保护标题和omp_set_num_threads(threads); 当您不想使用 OpenMP 编译指示但仍希望识别 OpenMP 库函数时,更简单的选择是将 -fopenmp 替换为 -lgomp。此方法是一种等效于 stub OpenMP 编译器开关的方法。 OMP_NUM_THREADS 更简单,你的代码中根本不需要任何东西。【参考方案3】:

如果您不向编译器传递附加标志,一个解决方案是使用预处理器忽略 pragma 语句。

例如,在您的代码中,您可能有:

#ifdef MP_ENABLED
#pragma omp parallel for
#endif
for(...)
  ...

然后在编译时可以将标志传递给编译器以定义 MP_ENABLED 宏。对于 GCC (and Clang),您将通过 -DMP_ENABLED

然后你可以用 gcc as 编译

gcc SOME_SOURCE.c -I SOME_INCLUDE.h -lomp -DMP_ENABLED -o SOME_OUTPUT

然后,当您想禁用并行性时,您可以通过删除 -DMP_ENABLED 对编译命令进行小调整。

gcc SOME_SOURCE.c -I SOME_INCLUDE.h -lomp -DMP_ENABLED -o SOME_OUTPUT

这会导致宏未定义,从而导致预处理器忽略编译指示。

您也可以使用 ifndef 来使用类似的解决方案,具体取决于您是否将并行行为视为默认行为。

编辑:正如一些 cmets 所指出的,包含 OMP 库定义了一些宏,例如 _OPENMP,您可以使用它们来代替您自己的用户定义的宏。这看起来是一个更好的解决方案,但工作量的差异相当小。

【讨论】:

我尝试了类似的方法,但没有想到使用编译器/Makefile 来传递标志...这对我来说似乎是一个不错的选择! -fopenmp在符合标准的编译器下定义_OPENMP时,为什么还要给-DMP_ENABLED添加用户需求? 关于 __OPENMP 的要点。不确定 OP 是否使用了符合要求的编译器。

以上是关于仅在将参数传递给程序时使用 openMP的主要内容,如果未能解决你的问题,请参考以下文章

尝试在其线程函数参数中将不同的值传递给 openMp 线程

在 GCC 中将 0 参数传递给可变参数宏失败,但仅在 C++ 中?

将 DateTime 值作为参数传递给 OleDbCommand

如何将参数传递给反序列化json的构造函数

使用 ForEach 中的 SwiftUI 3.0 .swipeActions,您如何在将 ForEach 的输入参数传递给该视图时让一个动作转到另一个视图?

通过javascript将参数传递给动作不会传递整数值