Есть ли способ явно указать компилятору остановить оптимизацию?

Linux, C, GCC, ARM (Allwinner H3). Можно ли получить следующий эквивалентный функционал (псевдокод):

#pragma optimizations-stop
#pragma registers-flush

a=10;
while(*b != 0) c++;
if (a == 0) { do something; }

#pragma optimizations-start

если результат местоположения a явно записан со значением 10, то значение каждого цикла цикла b считывается из памяти, а затем считанное значение из местоположения b сравнивается с 0, а затем, если не ноль, c считывается с +1 , а когда цикл прерывается, местоположение a явно считывается из памяти и сравнивается с 0?

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

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

Если это невозможно на уровне C/GCC, я буду рад предложениям реализовать это как встроенную сборку.

Это довольно просто, объявив a, b и c как volatile. Это то, что вам нужно?

Konrad Rudolph 09.07.2024 08:57

Конечно, нет. Смотрите вопрос, который я задал здесь stackoverflow.com/questions/78722496/…. Кроме того, я узнал, что сам процессор (особенно ARM) будет решать за меня, как выполнять МОЮ программу.

Anonymous 09.07.2024 08:59

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

Konrad Rudolph 09.07.2024 09:06

В любом случае: если ваш вопрос: «Как отключить кеш ЦП и предотвратить предварительную выборку инструкций и т. д.?» тогда я думаю, что ответ: вы не можете. (Хотя я могу ошибаться)

Konrad Rudolph 09.07.2024 09:08

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

Lundin 09.07.2024 09:30

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

Lundin 09.07.2024 09:54
В GCC есть #pragma GCC optimize(…), который отвечает на вопрос заголовка. Мне не нужно было использовать «большой молот» (это Мьёльнир?), поэтому я не уверен, лучше это, хуже или просто отличается от прагмы. IME, прагма обычно используется в отношении сомнительного кода, который неправильно обрабатывается компилятором — часто это целая функция, иногда несколько функций, иногда часть функции.
Jonathan Leffler 09.07.2024 17:01

«Кроме того, я узнал, что сам процессор (особенно ARM) будет решать за меня, как выполнять МОЮ программу». Да, именно так работают современные процессоры, и во многом именно поэтому они работают так быстро. Если вы категорически против этой концепции, то лучшим выбором, вероятно, будет машина времени, позволяющая вернуться в 1982 год.

Nate Eldredge 10.07.2024 02:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
161
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

«Большой молоток» — это расширение GCC, asm __volatile__("": : :"memory"). Это полный барьер памяти, используемый ядром Linux.

Обратите внимание, что это не делает то, что вы просите в заголовке. Это совершенно не мешает оптимизации. Однако он достигает того порядка, который вы описываете.

Также обратите внимание, что c++ — это чтение-изменение-запись. Он не атомарный, и между частями нет барьеров памяти. Компилятор может разумно хранить c в регистре и выполнять в реестре чтение-изменение-запись. Чтобы избежать этого, вам понадобится что-то вроде volatile int* pc = &c;.

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

Можно ли использовать atomic_thread_fence(memory_order_seq_cst); (#include <stdatomic.h>) в качестве барьера памяти?

Ian Abbott 09.07.2024 11:57

«чтобы избежать этого, вам понадобится что-то вроде volutous int* pc = &c;»: даже если доступ осуществляется через volatile, я не думаю, что существует какая-либо гарантия, что приращение будет выполнено за одну комбинированную загрузку/сохранение. Если я правильно помню, между GCC и Clang были даже расхождения в реализации.

user17732522 09.07.2024 12:52

@user17732522 user17732522: Верно. Вы запрещаете только часть «хранить в регистре при увеличении». Он определенно не атомарный и не синхронизируется с другими потоками.

MSalters 09.07.2024 13:13
asm __volatile__("": : :"memory") кажется, это всего лишь барьер переупорядочения во время компиляции. Никаких инструкций по синхронизации памяти он не добавляет (godbolt.org/z/3z6o3h9xb). Поэтому я не уверен, достаточно ли этого для решения проблем ОП. На x86 это будет вести себя как получение/выпуск, но не как последовательная согласованность. Этого достаточно для ОП, но, например. на ARM я не думаю, что это дает достаточные гарантии порядка памяти.
user17732522 09.07.2024 21:47

Использование _Atomic для всех рассматриваемых переменных позволит достичь этого:

  1. Каждое чтение и запись в переменную _Atomic будет соответствовать фактической выполняемой инструкции загрузки или сохранения. (Хотя это не является формальной гарантией стандарта C, на практике компиляторы не пытаются его оптимизировать.)

  2. Компилятор не будет переупорядочивать эти загрузки и хранилища.

  3. Инструкции барьера памяти будут вставлены там, где это необходимо, чтобы предотвратить любое изменение порядка ЦП и гарантировать, что они соблюдаются в том же порядке всеми остальными потоками/ЦП, которые также используют соответствующие барьеры. (По умолчанию порядок памяти для атомарности — последовательная согласованность. Чтобы избежать любого из этих барьеров памяти, вам нужно использовать такие функции, как atomic_load_explicit).

  4. c++ будет выполняться с помощью атомарной инструкции чтения-изменения-записи или последовательности инструкций (например, ARM LDREX/STREX), так что никакая запись в c не может быть выполнена каким-либо другим потоком между чтением и записью.

  5. Доступ к переменным, отличным от _Atomic, не будет переупорядочен ни после сохранения, ни перед загрузкой. (Вы не можете предотвратить переупорядочение в других направлениях, кроме как путем создания других переменных _Atomic.)

Обратите внимание, что использование volatile, как предложено в другом месте, достигает 1 и 2, но обычно не 3, 4, 5. Более того, любой несинхронизированный одновременный доступ нескольких потоков к переменной, которая не является _Atomic, даже если это volatile, вызывает неопределенное поведение для вся программа по стандарту C (гонка данных).

Другие вопросы по теме