Подтвердить что ты не робот

Можно ли безопасно использовать OpenMP с С++ 11?

В стандарте OpenMP рассматриваются только С++ 98 (ISO/IEC 14882: 1998). Это означает, что нет стандартного поддерживающего использования OpenMP в С++ 03 или даже С++ 11. Таким образом, любая программа, использующая С++ > 98 и OpenMP, работает вне стандартов, подразумевая, что даже если она работает при определенных условиях, она вряд ли будет переносной, но определенно никогда не будет гарантирована.

Ситуация еще хуже с С++ 11 с собственной поддержкой многопоточности, которая, скорее всего, столкнется с OpenMP для определенных реализаций.

Итак, насколько безопасно использовать OpenMP с С++ 03 и С++ 11?

Можно ли безопасно использовать многопоточность С++ 11, а также OpenMP в одной и той же программе, но не чередуя их (т.е. никакой оператор OpenMP в любом коде, переданном на параллельные функции С++ 11, и не С++ 11 concurrency в потоках, порожденных OpenMP)?

Меня особенно интересует ситуация, когда я сначала вызываю некоторый код с помощью OpenMP, а затем другой код с использованием С++ 11 concurrency в тех же структурах данных.

4b9b3361

Ответ 1

Уолтер, я считаю, что я не только рассказал вам текущее состояние вещей в что другие обсуждения, но также предоставил вам информацию непосредственно из источника (т.е. из моего коллега, который является частью комитета OpenMP Language).

OpenMP был разработан как облегченное параллельное дополнение к FORTRAN и C, позднее расширенное к идиомам С++ (например, параллельные петли над итераторами с произвольным доступом) и к задаче parallelism с введением явных задач. Он предназначен как переносимый как можно больше платформ и обеспечивающий практически ту же функциональность на всех трех языках. Его модель исполнения довольно проста: однопоточное приложение создает команды потоков в параллельных областях, запускает некоторые вычислительные задачи внутри и затем объединяет команды в последовательное исполнение. Каждый поток из параллельной команды может позже разветкить собственную команду, если включен вложенный parallelism.

Поскольку основное использование OpenMP в High Performance Computing (в конце концов, его модель директивы и исполнения была заимствована из High Performance Fortran), основной целью любой реализации OpenMP является эффективность, а не совместимость с другими парадигмами потоков. На некоторых платформах эффективная реализация может быть достигнута только в том случае, если время выполнения OpenMP является единственным, контролирующим потоки процесса. Также есть некоторые аспекты OpenMP, которые могут плохо воспроизводиться с другими конструкциями потоковой передачи, например, ограничение количества потоков, заданных OMP_THREAD_LIMIT при использовании двух или более параллельных параллельных областей.

Так как сам стандарт OpenMP не запрещает строго использовать другие парадигмы потоков, но ни стандартизирует совместимость с такими, поддерживая такую ​​функциональность, не соответствует реализаторам. Это означает, что некоторые реализации могут обеспечить безопасное одновременное выполнение областей OpenMP верхнего уровня, а некоторые - нет. Разработчики x86 обязуются поддерживать его, возможно, потому, что большинство из них также являются сторонниками других моделей исполнения (например, Intel с Cilk и TBB, GCC с С++ 11 и т.д.), А x86 обычно считается "экспериментальной" платформой ( другие поставщики обычно гораздо более консервативны).

OpenMP 4.0 также не идет дальше, чем ISO/IEC 14882: 1998 для функций С++, которые он использует (проект SC12 здесь), Стандарт теперь включает в себя такие вещи, как переносимость потоковой нити - это определенно не очень хорошо работает с другими парадигмами потоков, которые могут обеспечить свои собственные механизмы привязки, которые сталкиваются с проблемами OpenMP. Еще раз, язык OpenMP ориентирован на HPC (параллельные научные и инженерные приложения для данных и задач). Конструкции С++ 11 ориентированы на универсальные вычислительные приложения. Если вы хотите использовать одновременно С++ 11, то используйте только С++ 11, или если вам действительно нужно смешать его с OpenMP, то придерживайтесь подмножества языков С++ 98, если вы хотите оставаться портативным.

Меня особенно интересует ситуация, когда я сначала вызываю некоторый код с помощью OpenMP, а затем другой код с использованием С++ 11 concurrency в тех же структурах данных.

Нет никаких очевидных причин, по которым вы не хотите, но это зависит от вашего компилятора OpenMP и времени выполнения. Существуют бесплатные и коммерческие библиотеки, которые используют OpenMP для параллельного выполнения (например, MKL), но всегда есть предупреждения (хотя иногда они глубоко скрыты в руководствах пользователя) о возможной несовместимости с многопоточным кодом, которые предоставляют информацию о том, что и когда это возможно. Как всегда, это выходит за рамки стандарта OpenMP и, следовательно, YMMV.

Ответ 2

Мне действительно интересны высокопроизводительные вычисления, но OpenMP (в настоящее время) не обслуживает мои достаточно хорошо: он недостаточно гибкий (мой алгоритм не основан на петле)

Возможно, вы действительно ищете TBB? Это обеспечивает поддержку циклов и задач на основе parallelism, а также множество параллельных структур данных в стандартном С++ и является как переносным, так и открытым исходным кодом.

(Полная оговорка: я работаю для Intel, которые активно участвуют в TBB, хотя я действительно не работаю на на TBB, но на OpenMP:-); Я, конечно, не говорю для Intel!).

Ответ 3

Как и Джим Кони, я тоже сотрудник Intel. Я согласен с ним в том, что Intel Threading Building Blocks (Intel TBB) может быть хорошим вариантом, поскольку он имеет уровень на уровне цикла parallelism, такой как OpenMP, а также другие параллельные алгоритмы, параллельные контейнеры и функции нижнего уровня. И TBB пытается идти в ногу с текущим стандартом С++.

И для уточнения для Уолтера, Intel TBB включает в себя алгоритм parallel_reduce, а также высокоуровневую поддержку для атомистики и мьютексов.

Руководство пользователя Intel® Threading Building Blocks можно найти в http://software.intel.com/sites/products/documentation/doclib/tbb_sa/help/tbb_userguide/title.htm. В Руководстве пользователя представлен обзор функций в библиотеке.

Ответ 4

OpenMP часто (я не знаю исключений), реализованного поверх Pthreads, поэтому вы можете рассуждать о некоторых вопросах взаимодействия, думая о том, как С++ 11 concurrency взаимодействует с кодом Pthread.

Я не знаю, является ли чрезмерная подписка из-за использования нескольких моделей потоковой передачи для вас проблемой, но это определенно проблема для OpenMP. Существует предложение для решения этой проблемы в OpenMP 5. До тех пор, как вы решаете это, определяется реализация. Это тяжелые молоты, но вы можете использовать OMP_WAIT_POLICY (OpenMP 4.5+), KMP_BLOCKTIME (Intel и LLVM) и GOMP_SPINCOUNT (GCC), чтобы решить эту проблему. Я уверен, что в других реализациях есть что-то подобное.

Одна проблема, в которой интероперабельность представляет собой реальную проблему, - w.r.t. модель памяти, т.е. как ведут себя атомные операции. Это в настоящее время undefined, но вы все еще можете рассуждать об этом. Например, если вы используете атомарность С++ 11 с OpenMP parallelism, вы должны быть в порядке, но вы несете ответственность за правильное использование атома атома С++ 11 из потоков OpenMP.

Смешивание атома атома OpenMP и атома С++ 11 - плохая идея. Мы (рабочая группа по языковому комитету OpenMP, которому поручено смотреть на поддержку базового языка OpenMP 5), в настоящее время пытаются разобраться в этом. Лично я считаю, что атомистика С++ 11 лучше, чем атомы OpenMP во всех отношениях, поэтому моя рекомендация заключается в том, что вы используете С++ 11 (или C11 или __atomic) для вашего атомизма и оставьте #pragma omp atomic для программистов Fortran.

Ниже приведен пример пример кода, который использует атомы С++ 11 с потоками OpenMP. Он работает так, как он был разработан везде, где я его тестировал.

Полное раскрытие: Как и Джим и Майк, я работаю для Intel: -)

#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