45

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

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

那么,在 C++03 和 C++11 中使用 OpenMP 有多安全?

是否可以在同一个程序中安全地使用 C++11 多线程和 OpenMP 但不交错(即在传递给 C++11 并发特性的任何代码中都没有 OpenMP 语句,并且线程中没有 C++11 并发由 OpenMP 产生)?

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

4

5 回答 5

28

Walter,我相信我不仅在其他讨论中告诉了您当前的情况,而且还直接从源头(即我的同事,他是 OpenMP 语言委员会的成员)为您提供了信息。

OpenMP 被设计为 FORTRAN 和 C 的轻量级数据并行补充,后来扩展到 C++ 习惯用法(例如,随机访问迭代器上的并行循环)和引入显式任务的任务并行性。它的目的是尽可能跨平台移植,并在所有三种语言中提供基本相同的功能。它的执行模型非常简单——单线程应用程序在并行区域中分叉线程团队,在内部运行一些计算任务,然后将团队重新加入串行执行。如果启用了嵌套并行,来自并行团队的每个线程都可以在以后派生出自己的团队。

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

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

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

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

没有明显的原因可以说明您不希望发生什么,但这取决于您的 OpenMP 编译器和运行时。有使用 OpenMP 进行并行执行的免费和商业库(例如 MKL),但总是有警告(尽管有时隐藏在其用户手册中很深)可能与多线程代码不兼容,这些代码提供了有关何时何地可能的信息。与往常一样,这超出了 OpenMP 标准的范围,因此也超出了 YMMV 的范围。

于 2012-12-12T12:24:11.750 回答
7

我实际上对高性能计算感兴趣,但 OpenMP(目前)并不能很好地满足我的目的:它不够灵活(我的算法不是基于循环的)

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

(完全免责声明:我为与 TBB 密切相关的英特尔工作,尽管我实际上并不从事TBB 工作而是从事 OpenMP 工作 :-);我当然不是在为英特尔说话!)。

于 2012-12-13T09:27:05.840 回答
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找到英特尔® 线程构建模块的用户指南 。用户指南概述了图书馆。

于 2012-12-17T18:06:10.497 回答
4

OpenMP 通常(我知道没有例外)是在 Pthread 之上实现的,因此您可以通过思考 C++11 并发如何与 Pthread 代码互操作来推断一些互操作性问题。

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

真正关心互操作性的一个问题是内存模型,即原子操作的行为方式。这是目前未定义的,但您仍然可以推理它。例如,如果您使用具有 OpenMP 并行性的 C++11 原子,则应该没问题,但您有责任在 OpenMP 线程中正确使用 C++11 原子。

混合 OpenMP atomics 和 C++11 atomics 是个坏主意。我们(负责研究 OpenMP 5 基本语言支持的 OpenMP 语言委员会工作组)目前正试图解决这个问题。就个人而言,我认为 C++11 原子在各个方面都比 OpenMP 原子要好,所以我的建议是你使用 C++11(或 C11,或__atomic)作为你的原子,而留给#pragma omp atomicFortran 程序员。

下面是一个将 C++11 原子与 OpenMP 线程结合使用的示例代码。它在我测试过的任何地方都按设计工作。

全面披露:像吉姆和迈克一样,我为英特尔工作:-)

#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
于 2016-10-04T19:53:53.690 回答
0

OpenMP 5.0 现在定义了与 C++11 的交互。但通常使用 C++11 中的任何内容,并进一步“可能导致未指定的行为”

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

  • 对齐支持
  • 标准布局类型
  • 允许移动构造投掷
  • 定义移动特殊成员函数
  • 并发
  • 数据依赖排序:原子和内存模型
  • 标准库的补充
  • 线程本地存储
  • 并发动态初始化和销毁
  • C++11 库
于 2018-11-28T10:00:41.597 回答