我可以安全地使用OpenMP和C ++ 11吗?

时间:2012-12-12 10:28:23

标签: c++ c++11 openmp

OpenMP标准仅考虑C ++ 98(ISO / IEC 14882:1998)。这意味着在C ++ 03甚至C ++ 11下没有标准的OpenMP支持用法。因此,任何使用C ++> 98和OpenMP的程序都在标准之外运行,这意味着即使它在某些条件下工作,它也不太可能是可移植的,但绝对不能保证。

C ++ 11具有自己的多线程支持,情况更糟,在某些实现中很可能与OpenMP发生冲突。

那么,将OpenMP与C ++ 03和C ++ 11一起使用有多安全?

可以安全地在一个相同的程序中使用C ++ 11多线程以及OpenMP但不交错它们(即在传递给C ++ 11并发功能且没有C ++ 11的任何代码中没有OpenMP语句OpenMP产生的线程中的并发性?

我特别感兴趣的是我首先使用OpenMP调用某些代码,然后在相同的数据结构上使用C ++ 11并发代码调用其他代码。

5 个答案:

答案 0 :(得分:24)

Walter,我相信我不仅告诉了你that other discussion中当前的状态,还直接从源头(即来自我的OpenMP语言委员会成员的同事)提供了信息。 / p>

OpenMP被设计为FORTRAN和C的轻量级数据并行添加,后来扩展为C ++习语(例如,随机访问迭代器上的并行循环),并通过引入显式任务来实现任务并行性。它意味着在尽可能多的平台上作为 portable ,并在所有三种语言中提供基本相同的功能。它的执行模型非常简单 - 单线程应用程序在并行区域中分配线程团队,在内部运行一些计算任务,然后将团队重新连接到串行执行。如果启用了嵌套并行性,并行团队中的每个线程都可以在以后分叉自己的团队。

由于OpenMP的主要用途是高性能计算(毕竟,它的指令和执行模型来自High Performance Fortran),任何OpenMP实现的主要目标都是效率而不是互操作性与其他线程范例。在某些平台上,只有在OpenMP运行时是控制进程线程的唯一运行时,才能实现高效实现。此外,OpenMP的某些方面可能无法与其他线程结构一起使用,例如,在分叉两个或多个并发并行区域时,OMP_THREAD_LIMIT设置的线程数限制。

由于OpenMP标准本身并没有严格禁止使用其他线程范例,但都没有标准化与此类的互操作性,因此支持此类功能取决于实施者。这意味着某些实现可能会提供顶级OpenMP区域的安全并发执行,有些可能不会。 x86实现者承诺支持它,可能是因为他们中的大多数也是其他执行模型的支持者(例如,带有Cilk和TBB的英特尔,带有C ++ 11的GCC等),x86通常被认为是“实验性”平台(其他供应商通常更加保守。)

OpenMP 4.0对于它采用的C ++功能(SC12草案为here)也没有比ISO / IEC 14882:1998更进一步。该标准现在包括诸如可移植线程亲和力之类的东西 - 这绝对不能与其他线程范例很好地结合,这可能提供它们自己的绑定机制,与OpenMP的绑定机制相冲突。 OpenMP语言再次针对HPC(数据和任务并行科学和工程应用程序)。 C ++ 11构造针对通用计算应用程序。如果你想要花哨的C ++ 11并发内容,那么只使用C ++ 11,或者如果你真的需要将它与OpenMP混合使用,那么如果你想保持便携的话,请坚持使用C ++ 98语言功能子集。 / p>

  

我特别感兴趣的是我首先使用OpenMP调用某些代码,然后在相同的数据结构上使用C ++ 11并发代码调用其他代码。

没有明显的理由可以实现您的想法,但这取决于您的OpenMP编译器和运行时。有一些免费和商业库使用OpenMP进行并行执行(例如MKL),但是总是有警告(尽管有时隐藏在用户手册中)可能与多线程代码不兼容,这些代码提供了可能的内容和时间的信息。与往常一样,这超出了OpenMP标准的范围,因此也是YMMV。

答案 1 :(得分:7)

  

我实际上对高性能计算很感兴趣,但OpenMP(目前)并没有为我提供服务   目的很好:它不够灵活(我的算法不是基于循环的)

也许你真的在寻找TBB? 这为标准C ++中基于循环和任务的并行性以及各种并行数据结构提供了支持,并且既可移植又可以开源。

(完全免责声明:我为那些积极参与TBB的英特尔工作,虽然我实际上并没有 TBB,但是在OpenMP上工作:-);我当然不是在为英特尔发言!)。

答案 2 :(得分:5)

和Jim Cownie一样,我也是英特尔员工。我同意他的观点,英特尔线程构建模块(英特尔TBB)可能是一个不错的选择,因为它具有像OpenMP这样的循环级并行,但也有其他并行算法,并发容器和低级功能。 TBB试图跟上当前的C ++标准。

为了澄清Walter,英特尔TBB包括parallel_reduce算法以及对原子和互斥体的高级支持。

您可以在http://software.intel.com/sites/products/documentation/doclib/tbb_sa/help/tbb_userguide/title.htm找到英特尔®线程构建模块的用户指南。用户指南概述了库中的功能。

答案 3 :(得分:3)

OpenMP通常(我知道没有例外)在Pthreads之上实现,所以你可以通过考虑C ++ 11并发如何与Pthread代码互操作来推理一些互操作性问题。

我不知道由于使用多个线程模型而导致的超额订阅对您来说是个问题,但这绝对是OpenMP的一个问题。在OpenMP 5中有一个proposal来解决这个问题。在此之前,您如何解决这个问题是实现定义的。它们是重锤,但您可以使用OMP_WAIT_POLICY(OpenMP 4.5+),KMP_BLOCKTIME(英特尔和LLVM)和GOMP_SPINCOUNT(GCC)来解决此问题。我确信其他实现也有类似的东西。

互操作性是一个真正令人担忧的问题是w.r.t.内存模型,即原子操作的行为方式。目前尚未定义,但您仍可以对其进行推理。例如,如果你使用C ++ 11原子与OpenMP并行,你应该没问题,但你有责任正确地使用OpenMP线程中的C ++ 11原子。

混合OpenMP原子和C ++ 11原子是一个坏主意。我们(负责查看OpenMP 5基础语言支持的OpenMP语言委员会工作组)目前正试图解决这个问题。就个人而言,我认为C ++ 11原子在各个方面都优于OpenMP原子,所以我的建议是你使用C ++ 11(或C11,或__atomic)作为你的原子并留下#pragma omp atomic对于Fortran程序员。

下面是一个example code,它使用C ++ 11原子和OpenMP线程。它在我测试过的任何地方都按照设计工作。

完全披露:像Jim和Mike一样,我在英特尔工作: - )

#if defined(__cplusplus) && (__cplusplus >= 201103L)

#include <iostream>
#include <iomanip>

#include <atomic>

#include <chrono>

#ifdef _OPENMP
# include <omp.h>
#else
# error No OpenMP support!
#endif

#ifdef SEQUENTIAL_CONSISTENCY
auto load_model  = std::memory_order_seq_cst;
auto store_model = std::memory_order_seq_cst;
#else
auto load_model  = std::memory_order_acquire;
auto store_model = std::memory_order_release;
#endif

int main(int argc, char * argv[])
{
    int nt = omp_get_max_threads();
#if 1
    if (nt != 2) omp_set_num_threads(2);
#else
    if (nt < 2)      omp_set_num_threads(2);
    if (nt % 2 != 0) omp_set_num_threads(nt-1);
#endif

    int iterations = (argc>1) ? atoi(argv[1]) : 1000000;

    std::cout << "thread ping-pong benchmark\n";
    std::cout << "num threads  = " << omp_get_max_threads() << "\n";
    std::cout << "iterations   = " << iterations << "\n";
#ifdef SEQUENTIAL_CONSISTENCY
    std::cout << "memory model = " << "seq_cst";
#else
    std::cout << "memory model = " << "acq-rel";
#endif
    std::cout << std::endl;

    std::atomic<int> left_ready  = {-1};
    std::atomic<int> right_ready = {-1};

    int left_payload  = 0;
    int right_payload = 0;

    #pragma omp parallel
    {
        int me      = omp_get_thread_num();
        /// 0=left 1=right
        bool parity = (me % 2 == 0);

        int junk = 0;

        /// START TIME
        #pragma omp barrier
        std::chrono::high_resolution_clock::time_point t0 = std::chrono::high_resolution_clock::now();

        for (int i=0; i<iterations; ++i) {

            if (parity) {

                /// send to left
                left_payload = i;
                left_ready.store(i, store_model);

                /// recv from right
                while (i != right_ready.load(load_model));
                //std::cout << i << ": left received " << right_payload << std::endl;
                junk += right_payload;

            } else {

                /// recv from left
                while (i != left_ready.load(load_model));
                //std::cout << i << ": right received " << left_payload << std::endl;
                junk += left_payload;

                ///send to right
                right_payload = i;
                right_ready.store(i, store_model);

            }

        }

        /// STOP TIME
        #pragma omp barrier
        std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

        /// PRINT TIME
        std::chrono::duration<double> dt = std::chrono::duration_cast<std::chrono::duration<double>>(t1-t0);
        #pragma omp critical
        {
            std::cout << "total time elapsed = " << dt.count() << "\n";
            std::cout << "time per iteration = " << dt.count()/iterations  << "\n";
            std::cout << junk << std::endl;
        }
    }

    return 0;
}

#else  // C++11
#error You need C++11 for this test!
#endif // C++11

答案 4 :(得分:0)

OpenMP 5.0现在定义了与C ++ 11的交互。但是,通常使用C ++ 11和更高版本的任何“可能会导致未指定的行为”

此OpenMP API规范将ISO / IEC 14882:2011称为C ++ 11。虽然OpenMP规范的未来版本有望解决以下功能,但当前使用它们可能会导致未指定的行为。

  • 对齐支持
  • 标准布局类型
  • 允许移动构造物抛出
  • 定义移动特殊成员函数
  • 并发
  • 数据相关性排序:原子和内存模型
  • 对标准库的添加
  • 线程本地存储
  • 并发动态初始化和销毁​​
  • C ++ 11库