Обработка исполнения с изменением последовательности

Я недавно наткнулся на эту статью Wikipedia. На основе моего опыта с многопоточностью я знаю о множестве проблем, вызванных способностью программы переключить потоки между потоками в любое время. Однако я никогда не знал, что компилятор и аппаратные оптимизации могли переупорядочить операции способом, который, как гарантируют, будет работать на единственный поток, но не обязательно для многопоточности. Кто-либо может объяснить, как правильно иметь дело с возможностью переупорядоченных операций в многопоточной среде?

ОБНОВЛЕНИЕ: Я первоначально случайно связался со статьей Out-of-Order Execution вместо статьи Барьера памяти, которая имеет лучшее объяснение проблемы.

16
задан Casebash 31 January 2010 в 11:41
поделиться

12 ответов

Я отвечу на Ваш вопрос как на вопрос о многопоточности на языке высокого уровня, а не об оптимизации процессорного конвейера.

Кто-нибудь может объяснить, как правильно поступать с возможностью переупорядочивания операций в многопоточной среде?

Большинство, если не все, то современные многопоточные языки высокого уровня предоставляют конструкции для управления этим потенциалом компилятора для переупорядочивания логического выполнения инструкций. В C# они включают в себя конструкции полевого уровня (volatile modifier), блочные конструкции (lock keyword) и императивные конструкции (Thead.MemoryBarrier).

Применение volatile к полю приводит к тому, что все обращения к этому полю в процессоре/памяти выполняются в том же относительном порядке, в котором оно встречается в последовательности команд (исходном коде).

Использование lock вокруг блока кода приводит к тому, что закрытая последовательность инструкций должна выполняться в том же относительном порядке, в котором она находится в родительском блоке кода.

Метод Thread.MemoryBarrier указывает компилятору, что процессор не должен переупорядочивать доступ к памяти вокруг этой точки в последовательности инструкций. Это позволяет использовать более продвинутый метод для специализированных требований.

Методы, описанные выше, описываются в порядке возрастания сложности и производительности. Как и при любом параллельном программировании, определение того, когда и где применять эти методы, является сложной задачей. При синхронизации доступа к одному полю будет работать ключевое слово volatile , но оно может оказаться слишком сложным. Иногда требуется только синхронизировать записи (в этом случае ReaderWriterLockSlim выполнит то же самое с гораздо лучшей производительностью). Иногда необходимо несколько раз в быстрой последовательности манипулировать полем, или же необходимо проверить поле и условно манипулировать им. В таких случаях лучше использовать ключевое слово lock. Иногда в очень слабо синхронизированной модели с целью повышения производительности (как правило, не рекомендуется) вы используете многопотоковое манипулирование общим состоянием. В этом случае тщательно размещенные барьеры памяти могут предотвратить использование в потоках несвежих и противоречивых данных.

8
ответ дан 30 November 2019 в 21:19
поделиться

Это не компилятор, это ЦП. (Ну как на самом деле, но ЦП сложнее контролировать.) Независимо от того, как ваш код будет компилироваться, ЦП будет выглядеть вперед в потоке инструкций и выполнять вещи из строя. Как правило, например, начать чтение рано, поскольку память медленнее, чем ЦП. (т. Е. Начните рано, надеемся, что чтение сделано, прежде чем на самом деле его нужно)

как CPU, так и компилятор оптимизируют на основе того же правила: переупорядочить что-либо, если оно не влияет на результаты программы * Предполагая одноретичную однопроцессорную среду * .

Итак, проблема - это оптимизирует для однопотоковой потребления, когда она не. Почему? Потому что иначе все будет 100x медленнее. В самом деле. И большая часть вашего кода SingleThereaded (то есть однопотоковое взаимодействие) - только небольшие детали должны взаимодействовать в многопоточной форме.

Лучший / самый простой / безопасный способ контролировать это с замками - mutexes, семафоры, событиями и т. Д.

Только если вы действительно, действительно должны оптимизировать (на основе тщательного измерения), то вы можете посмотреть Барьеры памяти и атомные операции - это основные инструкции, которые используются для создания мьютексов и т. Д., И при правильном использовании ограничивают выполнение вне заказа.

Но прежде чем делать такую ​​оптимизацию, убедитесь, что алгоритмы и кодовый поток верны и могут ли вы больше минимизировать многопоточные взаимодействия.

3
ответ дан 30 November 2019 в 21:19
поделиться

Давайте будем чистыми - из-за выполнения заказа относится к выполнению процессора, не компилятору как можно по SE , поскольку ваша ссылка четко демонстрирует.
Вне выполнения заказа является стратегия, используемая большинством современных трубопроводов CPU, которая позволяет им повторно заказать инструкции по лету, чтобы он, как правило, минимизирует прочитанные / писать киоски, которая является наиболее распространенным узким местом для современного оборудования из-за неисправности между скоростями выполнения процессоров и памятью Задержка (т.е. как быстро мой процессор может получить и обрабатывать по сравнению с тем, как быстро я могу обновить результат обратно в память).
Таким образом, это прежде всего аппаратное обеспечение, а не функция компилятора.
Вы можете переопределить эту функцию, если вы знаете, что вы делаете, как правило, используете барьеров памяти . Power PC имеет удивительно названную инструкцию, называемую EIEIO (Enforce в порядке выполнения ввода / вывода), который заставляет CPU промыть все ожидающие чтения и записи в память - это особенно важно с одновременным программированием (будь то многопоточны или Процессор), поскольку он гарантирует, что все процессоры или потоки синхронизировали значение всех местоположений памяти.
Если вы хотите прочитать об этом в глубине, то Этот PDF - превосходное (хотя подробно) введение.
Х-х

3
ответ дан 30 November 2019 в 21:19
поделиться
r = (colour >> 16) & 0xff;
g = (colour >> 8) & 0xff;
b = colour & 0xff;
-121--3783614-

Компилятор не генерирует из ошибок выполнения, оно оптимизирует и пересматривает, однако он любит до тех пор, пока он производит дает результат, ваш исходный код должен.

Но в борьбе с мультихую, это действительно может взорвать, хотя это обычно имеет мало для того, чтобы компилятор переупорядочить ваш код (хотя он может сделать его хуже в других оптимистических случаях.

Работа с нитями, работающими на Те же данные, вы должны быть очень, очень осторожны и убедитесь, что ваши данные правильно охраняются с соответствующей охраной (семафоры / мьютреные / атомные операции и аналогичные)

2
ответ дан 30 November 2019 в 21:19
поделиться

Как вы предотвратите возможность вне выполнения функций выполнения и подрыва в вашем лице?

Вы не являетесь - компилятор может изменить только порядок выполнения, когда это не изменяет конечный результат.

0
ответ дан 30 November 2019 в 21:19
поделиться

Я понятия не имею, существует ли такой сайт, как вы, но вы можете использовать следующие методы, чтобы узнать об установке PHP:

Несколько трудно сказать: Это доступно в Windows, и это не является общим , поскольку часто функции зависят от версии PHP, и это может так же сильно повлиять на любую ОС.

-121--3016453-

Что-то подобное:

r = ( colour >> 16 ) & 0xFF;
g = ( colour >> 8 )  & 0xFF;
b = colour & 0xFF;

Предполагая 8-битные значения компонентов. Побитовые и шестнадцатеричные маски 0xFF выбирают только 8-разрядные значения для каждого компонента.

-121--3783615-

Большинство компиляторов в настоящее время имеют явные особенности упорядочения. C++ 0x также имеет характеристики упорядочивания памяти.

0
ответ дан 30 November 2019 в 21:19
поделиться

Дело в том, что если вы только начинаете иметь дело с многопоточным кодом (до такой степени, что вы явно говорите о планировании потоков, как будто это немного страшно [не сказать, что это не так, но по разным причинам]), то это происходит на гораздо, гораздо более низком уровне, чем вам нужно беспокоиться. Как говорили другие, компиляторы не будут этого делать, если это не может гарантировать корректность, и хотя хорошо знать, что такие технологии существуют, если только вы не пишете свой собственный компилятор или не делаете действительно "голые металлические" вещи, это не должно представлять проблемы.

1
ответ дан 30 November 2019 в 21:19
поделиться

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

Поскольку ни C NO C ++ не имели сильно определенную модель памяти, компиляторы могут Оптимация по поводу указания постановки, что может вызвать проблемы для мультипоток. Но как для компиляторов, которые предназначены для использования в многопоточных средах, они не .

Multi-Threade Code либо пишет в память, и использует забор для обеспечения видимости пишет между потоками, либо использует атомные операции.

Поскольку значения, используемые в корпусе атомных операций, наблюдаются в одном потоке, переупорядочение не влияет на это - они должны были быть правильно рассчитаны до атомной операции.

Повалки, предназначенные для многопоточных приложений, не заменяют порядок в памяти.

Так что переупорядочение либо не влияет на поведение, либо подавляется как особый случай.

Если вы уже пишете правильный мульти-резьбовый код, компилятор переупорядочение не имеет значения. Это только проблема, если компилятор не знает о заборах с памятью, той случай, если вы, вероятно, не должны использовать его, чтобы написать многопоточный код в первую очередь.

1
ответ дан 30 November 2019 в 21:19
поделиться

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

-1
ответ дан 30 November 2019 в 21:19
поделиться

Позвольте мне задать вопрос: учитывая программный код (скажем, это однопоточное приложение), каково правильное выполнение? Интуитивно понятно, что выполнение ЦП в порядке, указанном в коде, было бы правильным .Эта иллюзия последовательного выполнения - вот что есть у программистов.

Однако современные процессоры не подчиняются этому ограничению. Если зависимости не нарушаются (зависимость от данных, зависимость от управления и зависимость от памяти), процессоры выполняют инструкции не по порядку. Однако для программистов это полностью скрыто. Программисты никогда не видят, что происходит внутри процессора.

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

Если вы рассматриваете только однопоточное приложение, вам не нужно беспокоиться о таком неупорядоченном выполнении компиляторами и процессорами для одного -резьбовой чехол.

(Чтобы узнать больше, я рекомендую вам взглянуть на концепцию ILP (параллелизм на уровне инструкций) . Производительность одного потока в основном зависит от того, сколько ILP вы можете извлечь из одного потока Таким образом, и процессоры, и компиляторы делают все, что могут, для повышения производительности.)

Однако, когда вы рассматриваете многопоточное выполнение, возникает потенциальная проблема, называемая проблемой согласованности памяти .Интуитивно программисты имеют понятие последовательной согласованности . Однако современные многоядерные архитектуры проводят грязную и агрессивную оптимизацию (например, кеши и буферы). В современной компьютерной архитектуре сложно реализовать последовательную согласованность с низкими накладными расходами. Таким образом, может возникнуть очень запутанная ситуация из-за неупорядоченного выполнения загрузки и сохранения памяти. Вы можете заметить, что некоторые загрузки и хранилища были выполнены не по порядку. Прочтите некоторые статьи, связанные с моделями ослабленной памяти , такими как модель памяти Intel x86 (прочтите главу 8, Упорядочивание памяти, том 3A Руководства разработчика программного обеспечения Intel 64 и архитектуры IA-32). Барьеры памяти необходимы в этой ситуации, когда вам нужно принудительно установить порядок инструкций памяти для правильности.

ОТВЕТ НА ВОПРОС : Кратко ответить на этот вопрос непросто. Нет хороших инструментов, которые обнаруживают такое неупорядоченное и проблемное поведение из-за модели согласованности памяти (хотя есть исследовательские статьи). Короче говоря, вам даже сложно найти такие ошибки в вашем коде. Однако я настоятельно рекомендую вам прочитать статьи о блокировке с двойной проверкой и ее подробную статью . В случае блокировки с двойной проверкой из-за ослабления согласованности памяти и переупорядочения компиляторов (обратите внимание, что компиляторы не знают о многопоточном поведении, если вы явно не указали барьеры памяти), это может привести к неправильному поведению.

В итоге:

  • Если вы работаете только с однопоточной программой, вам не нужно беспокоиться о неправильном поведении.
  • В многоядерных системах вам может потребоваться рассмотреть проблемы согласованности памяти. Но на самом деле это редко, когда вам действительно нужно беспокоиться о проблеме согласованности памяти. По большей части, гонки данных , тупиковые ситуации и нарушения атомарности убивают вашу многопоточную программу.
8
ответ дан 30 November 2019 в 21:19
поделиться

И компилятор, и центральный процессор реализуют алгоритмы, обеспечивающие сохранение последовательной семантики для данного потока выполнения. Если они не реализуют указанные алгоритмы, это считается ошибкой. Можно с уверенностью предположить, что переупорядочение инструкций не повлияет на семантику вашей программы.

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

В ответ на ваш заголовок: Вы не занимаетесь исполнением ООО.

1
ответ дан 30 November 2019 в 21:19
поделиться

По сути, вы спрашиваете о модели согласованности памяти. Некоторые языки / среды, такие как Java и .NET, определяют модель памяти, и программист несет ответственность за недопущение действий, которые запрещены или приводят к неопределенному поведению. Если вы не уверены в атомарности «обычных» операций, лучше перестраховаться и просто использовать примитивы мьютекса.

Для C и C ++ ситуация не так хороша, поскольку эти языковые стандарты не определяют модель памяти. И нет, вопреки, к сожалению, популярному мнению, volatile ничего не гарантирует относительно атомарности. В этом случае вы должны полагаться на библиотеку потоков платформы (которая, среди прочего, выполняет необходимые барьеры памяти) или атомарные встроенные функции, специфичные для компилятора / hw, и надеяться, что компилятор не выполняет никаких оптимизаций, нарушающих семантику программы. Пока вы избегаете условной блокировки внутри функции (или единицы трансляции при использовании IPA), вы должны быть в относительной безопасности.

К счастью, C ++ 0x и следующий стандарт C исправляют эту проблему, определяя модель памяти. Я задал вопрос, связанный с этим и, как оказалось, условной блокировкой здесь ; вопрос содержит ссылки на некоторые документы, которые более подробно освещают проблему. Я рекомендую вам прочитать эти документы.

1
ответ дан 30 November 2019 в 21:19
поделиться
Другие вопросы по тегам:

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