Поиск URL-адреса LDAP?

Синтаксический сахар, делает его более очевидным для случайного читателя, что соединение не является внутренним.

121
задан teroi 11 December 2018 в 18:30
поделиться

1 ответ

В C++ 11, обычно никогда не используйте volatile для поточной обработки, только для MMIO

Для атомной энергетики самокрутки с volatile (и встроенный-asm для барьеров) в плохие былые времена перед C++ 11 std::atomic, volatile как единственный хороший способ получить некоторые вещи работать. Но это зависело от большого количества предположений о том, как обработанные реализации и никогда не гарантировались никаким стандартом.

, Например, ядро Linux все еще использует свою собственную скрученную вручную атомную энергетику с volatile, но только поддерживает несколько определенных реализаций C (GNU C, лязг и возможно ICC). Частично поэтому GNU C расширения и встроенный asm синтаксис и семантика, но также и потому что это зависит от некоторых предположений о том, как работают компиляторы.

<час>

Тем не менее volatile применимо на практике для вещей как exit_now флаг на всех (?) существующих реализациях C++ на реальных центральных процессорах, из-за того, как центральные процессоры работают (когерентные кэши) и совместно использованные предположения о том, как volatile должен работать. , Но не очень еще, и не рекомендованы. цель этого ответа состоит в том, чтобы объяснить, как на самом деле работают существующие центральные процессоры и реализации C++. Если Вы не заботитесь об этом, все, что необходимо знать, то, что std::atomic с mo_relaxed obsoletes volatile для поточной обработки.

(Стандарт C++ ISO довольно неопределенен на нем, просто говоря, что volatile доступы должны быть оценены строго согласно правилам машины краткого обзора C++, не оптимизированной далеко. Учитывая, что реальные реализации используют адресное пространство памяти машины для моделирования адресного пространства C++, это означает volatile, чтения и присвоения должны скомпилировать в инструкции по загрузке и хранению получить доступ к объектному представлению в памяти.)

<час>

, Поскольку другой ответ указывает, exit_now, флаг является простым случаем коммуникации межпотока, которой не нужна никакая синхронизация : это не публикует тот массив, содержание готово или что-либо как этот. Просто хранилище это замечено быстро загрузкой not-optimized-away в другом потоке.

    // global
    bool exit_now = false;

    // in one thread
    while (!exit_now) { do_stuff; }

    // in another thread, or signal handler in this thread
    exit_now = true;

Без энергозависимого или атомарного, , как будто правило и предположение ни о какой гонке данных UB позволяют компилятору оптимизировать его в asm, который только проверяет флаг однажды , прежде, чем ввести (или не) бесконечный цикл. Это точно, что происходит в реальной жизни для реальных компиляторов. (И обычно оптимизируйте далеко большую часть из [1 115], потому что цикл никогда не выходит, таким образом, любой более поздний код, который, возможно, использовал результат, не достижим, если мы вводим цикл).

 // Optimizing compilers transform the loop into asm like this
    if (!exit_now) {        // check once before entering loop
        while(1) do_stuff;  // infinite loop
    }

программа Многопоточности всунула оптимизированный режим, но выполнения обычно в-O0 являются примером (с описанием вывода asm GCC) того, как точно это происходит с GCC на x86-64. Также программирование MCU - C++ оптимизация O2 повреждает цикл с условием продолжения на электронике. SE показывает другой пример.

Мы обычно хотим агрессивная оптимизация, включая которую CSE и поднимает загрузки из циклов, для глобальных переменных.

, Прежде чем C++ 11, volatile bool exit_now был одним путем для создания этой работы, как предназначено (на нормальных реализациях C++). Но в C++ 11, гонка данных UB все еще относится volatile, таким образом, это не на самом деле , гарантировал стандартом ISO для работы везде.

Примечание, что для более широких типов, volatile не дает гарантии отсутствия разрыва. Я проигнорировал то различие здесь для [1 119], потому что это - надуманный вопрос на нормальных реализациях. Но это - также часть того, почему volatile все еще подвергается гонке данных UB вместо того, чтобы быть эквивалентным атомарному расслабленному.

(я не знаю ни о каких реализациях C++, которые работают std::thread потоки через ядра процессора без когерентных кэшей, все же. Некоторые асимметричные микросхемы ARM существуют с общим физическим адресным пространством, но не внутренние совместно используемые домены кэша. Так не когерентный. (например, поток комментария ядро A8 и Кора-M3 как TI Sitara AM335x). Но обычно различные ядра работали бы на тех ядрах, ни один образ системы, который мог выполнить потоки через оба ядра.)

Примечание, которое, "как предназначено" не означает поток, делающий exit_now, ожидает другого потока для фактического выхода. Или даже что это ожидает энергозависимого exit_now=true хранилище, чтобы даже быть глобально видимым прежде, чем продолжиться к более поздним операциям в этом потоке. (atomic<bool> со значением по умолчанию mo_seq_cst дал бы Вам это).

C++ 11 обеспечивает путь non-UB, который компилирует тот же

, А "продолжают бежать", или "выходят теперь" из флага, должен использовать std::atomic<bool> flag с [1 127]

Используя [1 193]

  • flag.store(true, std::memory_order_relaxed)
  • while( !flag.load(std::memory_order_relaxed) ) { ... }

, даст Вам тот же самый asm (без дорогих инструкций по барьеру), что Вы добрались бы от [1 130].

, А также без разрывов, atomic также дает Вам способность сохранить в одном потоке и загрузке в другом без UB, таким образом, компилятор не может поднять загрузку из цикла. (Предположение ни о какой гонке данных, UB - то, что позволяет агрессивную оптимизацию, которую мы хотим для неатомарных энергонезависимых объектов.) Этой функцией [1 132] является в значительной степени то же как, что volatile делает для чистых загрузок и чистых хранилищ.

atomic<T> также делают += и так далее в атомарные операции RMW (значительно более дорогой, чем атомарная загрузка во временный файл, действуйте, затем отдельное атомарное хранилище. Если Вы не хотите атомарный RMW, напишите свой код с локальным временным файлом).

Со значением по умолчанию seq_cst упорядочивание Вы добрались бы от [1 137], оно также добавляет, что упорядочивание гарантирует wrt. неатомарные доступы, и к другим атомарным доступам.

(В теории, стандарт C++ ISO не исключает оптимизацию времени компиляции атомной энергетики. Но в компиляторах практики не делают , потому что нет никакого способа управлять, когда это не было бы в порядке. Существует несколько случаев, где даже volatile atomic<T> не могло бы быть достаточно управления оптимизацией атомной энергетики, если бы компиляторы оптимизировали, поэтому на данный момент, компиляторы не делают. См. Почему don' t компиляторы объединяют избыточный станд.:: атомарные записи? Примечание, что wg21/p0062 рекомендует против использования volatile atomic в текущем коде принять меры против оптимизации атомной энергетики.)

<час>

volatile действительно на самом деле работает на это на реальных центральных процессорах (но все еще не используйте его)

даже со слабо заказанными моделями памяти (non-x86) . Но на самом деле не используйте его, используйте atomic<T> с [1 142] вместо этого!! Точка этого раздела должна обратиться к неправильным представлениям о том, как реальные центральные процессоры работают, для не выравнивания по ширине volatile. При написании незапертого кода Вы, вероятно, заботитесь о производительности. Понимание кэшей и затрат на коммуникацию межпотока обычно важно для хорошей производительности.

Реальные центральные процессоры имеют когерентные кэши / общая память: после того, как хранилище от одного ядра становится глобально видимым, никакое другое ядро не может загрузка устаревшее значение. (См. также , Программисты Мифов Верят о Кэшах ЦП , который говорит некоторые о летучих веществах Java, эквивалентных C++ atomic<T> с seq_cst порядком памяти.)

, Когда я говорю загрузка , я имею в виду asm инструкцию та память доступов. Это - то, что volatile доступ гарантирует и не то же самое как lvalue-to-rvalue преобразование неатомарного / энергонезависимая переменная C++. (например, local_tmp = flag или while(!flag)).

единственной вещью, которую необходимо победить, является оптимизация времени компиляции, которая не перезагружает вообще после первой проверки. Любой load+check на каждом повторении достаточен без любого упорядочивания. Без синхронизации между этим потоком и основным потоком, это не значимо для разговора о том, когда точно хранилище произошло, или упорядочивание загрузки wrt. другие операции в цикле. Только [1 171], когда это видимо к этому потоку , - то, что имеет значение. Когда Вы видите набор флага exit_now, Вы выходите. Межбазовая задержка на типичном x86 Xeon может быть что-то как 40 нс между отдельными физическими ядрами .

<час>

В теории у Вас могла быть реализация C++ на машине, которая не была похожа на это, требуя, чтобы явные сбросы сделали вещи видимыми к другим потокам . Стандарт C++ не делает это, модель памяти невозможного, но C++ разработана на основе того, чтобы быть эффективным на когерентных машинах общей памяти; нет никакого механизма для release хранилище, чтобы только сбросить себя и несколько избранных диапазонов адресов: это должно было бы синхронизировать все, потому что это не будет знать то, что другие потоки могли бы хотеть считать, если бы их получать-загрузка видела это хранилище выпуска (формирующий последовательность выпуска, которая устанавливает происхождение - перед отношениями через потоки, гарантируя, что ранее неатомарные операции, сделанные потоком записи, теперь безопасно считать. Если это не сделало дальнейшие записи им после хранилища выпуска...)

Связанный: мой ответ на [1 157] Является mov + mfence безопасный на NUMA? вдается в подробности о несуществовании x86 систем без когерентной общей памяти.

Там , я думаю кластеры с некогерентной общей памятью, но они не машины единственного образа системы. Каждый домен когерентности выполняет отдельное ядро, таким образом, Вы не можете выполнить потоки единственной программы C++ через него. Вместо этого Вы выполняете отдельные экземпляры программы (каждый с их собственным адресным пространством: указатели в одном экземпляре не допустимы в другом).

, Чтобы заставить их общаться друг с другом через явные сбросы, Вы обычно использовали бы MPI или другой API передачи сообщений, чтобы заставить программу определить, для каких диапазонов адресов нужно сбрасывание.

<час>

, Но в многоядерной системе с когерентными кэшами, реализовывая хранилище выпуска просто фиксация упорядочивания средств в кэш для хранилищ этого потока, не делая никакого явного сбрасывания. ( https://preshing.com/20120913/acquire-and-release-semantics / и https://preshing.com/20120710/memory-barriers-are-like-source-control-operations / ). (И получать-загрузка означает приказывать, чтобы доступ кэшировался в другом ядре).

инструкция по барьеру памяти А просто блокирует загрузки текущего потока и/или хранилища до буферных дренажей хранилища; это всегда происходит максимально быстро самостоятельно. ( барьер памяти гарантирует, что когерентность кэша была завершена? адреса это неправильное представление). Таким образом, если Вам не нужно упорядочивание, просто быстрая видимость в других потоках, mo_relaxed прекрасна. (И так volatile, но не делайте этого.)

Забавный факт: на x86 каждое хранилище asm хранилищем выпуска, потому что x86 модель памяти была в основном seq-cst плюс буфер хранилища (с передачей хранилища).

<час>

Полусвязанное ре: хранит буферную, глобальную видимость и когерентность: C++ 11 гарантий очень мало. Самые реальные ISAs (кроме PowerPC) действительно гарантирует, что все потоки договаривающиеся о порядке появления двух хранилищ двумя другими потоками.

3
ответ дан 24 November 2019 в 01:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: