Какие оптимизаторы предотвращают "volatile" в С++?

Я искал ключевое слово volatile и для чего он, и ответ, который я получил, был в значительной степени:

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

Были некоторые примеры, например, при опросе оборудования с отображением памяти: без volatile цикл опроса был удален, поскольку компилятор мог распознать, что значение условия никогда не изменяется. Но так как был только один пример или, может быть, два, это заставило меня задуматься: существуют ли другие ситуации, когда нам нужно использовать volatile с точки зрения избежания нежелательной оптимизации? Являются ли переменными условия единственным местом, где требуется volatile?

Я предполагаю, что оптимизация является специфичной для компилятора и поэтому не указана в спецификации С++. Означает ли это, что мы должны испытывать чувство кишки, говоря Hm, я подозреваю, что мой компилятор покончит с этим, если я не объявлю эту переменную как volatile, или есть какие-либо четкие правила для перехода по?

Ответ 1

В принципе, volatile объявляет, что значение может измениться за вашей программой. Это не позволяет компиляторам кэшировать значение (в регистре CPU) и оптимизировать доступ к этому значению, когда они кажутся ненужными из POV вашей программы.

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

Ответ 2

Наблюдаемое поведение программы на С++ определяется чтением и записью на изменчивые переменные и любыми вызовами функций ввода/вывода.

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

Это все. Он используется, когда вам нужно указать, что чтение или запись переменной следует рассматривать как наблюдаемый эффект. (Обратите внимание, что статья "С++ и опасность двойного контроля блокировки" затрагивает это довольно немного.)


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

Это означает, что изменяется компилятор:

int x = 2;
volatile int y = 5;
x = 5;
y = 7;

Для

int x = 5;
volatile int y = 5;
y = 7;

Прекрасно, так как значение x не является частью наблюдаемого поведения (оно не изменчиво). То, что было бы нехорошо, заключается в изменении задания от 5 до назначения на 7, потому что запись 5 является наблюдаемым эффектом.

Ответ 3

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

volatile гарантирует, что чтение и запись объекта не будут оптимизированы или переупорядочены относительно другого volatile. Если вы заняты циклом по переменной, измененной другим потоком, ее следует объявить volatile. Однако вы не должны быть заняты. Поскольку язык не был предназначен для многопоточности, это не очень хорошо поддерживается. Например, компилятор может переместить запись в энергонезависимую переменную после этого до цикла, нарушая блокировку. (Для неопределенных spinloops это может произойти только при С++ 0x.)

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

С++ 0x может не иметь этого недостатка, поскольку он вводит формальную многопоточную семантику. Я не очень хорошо знаком с изменениями, но для обратной совместимости не требуется объявлять что-либо волатильное, чего раньше не было.

Ответ 4

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

Ответ 5

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

Он может быть в регистре:

Его значение может быть вычислено, например. в:

int x = 2;
int y = x + 7;
return y + 1;

Не нужно иметь x и y вообще, но его можно просто заменить на:

return 10;

И еще один пример: любой код, который не влияет на состояние извне, может быть полностью удален. Например. если вы обнуляете конфиденциальные данные, компилятор может видеть это как потраченное впустую упражнение ( "почему вы пишете то, что не читается?" ) и удалите его. volatile может использоваться, чтобы остановить это.

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

(Заметка С#. Многое, что я видел в конце volatile, говорит о том, что люди читают о С++ volatile и применяют его к С#, и читают об этом на С# и применяют его к С++., volatile ведет себя так по-разному между этими двумя, чтобы не было полезно рассматривать их связанные).

Ответ 6

Если вы не находитесь во встроенной системе или вы пишете аппаратные драйверы, в которых используется сопоставление памяти в качестве средства связи, никогда не следует использовать volatile

Рассмотрим:

int main()
{
    volatile int SomeHardwareMemory; //This is a platform specific INT location. 
    for(int idx=0; idx < 56; ++idx)
    {
        printf("%d", SomeHardwareMemory);
    }
}

Необходимо создать код типа:

loadIntoRegister3 56
loadIntoRegister2 "%d"
loopTop:
loadIntoRegister1 <<SOMEHARDWAREMEMORY>
pushRegister2
pushRegister1
call printf
decrementRegister3
ifRegister3LessThan 56 goto loopTop

тогда как без volatile это может быть:

loadIntoRegister3 56
loadIntoRegister2 "%d"
loadIntoRegister1 <<SOMEHARDWAREMEMORY>
loopTop:
pushRegister2
pushRegister1
call printf
decrementRegister3
ifRegister3LessThan 56 goto loopTop

Предположение о volatile заключается в том, что местоположение памяти переменной может быть изменено. Вы вынуждаете компилятор загружать фактическое значение из памяти каждый раз, когда используется переменная; и вы сообщаете компилятору, что повторное использование этого значения в регистре не разрешено.

Ответ 7

Один из способов подумать об изменчивой переменной - это представить, что это виртуальное свойство; пишет и даже читает, может делать то, о ком компилятор не может знать. Фактически сгенерированный код для записи/чтения изменчивой переменной - это просто запись или чтение памяти (*), но компилятор должен считать код непрозрачным; он не может делать никаких предположений, при которых он может быть излишним. Проблема заключается не только в том, чтобы убедиться, что скомпилированный код замечает, что что-то вызвало изменение переменной. На некоторых системах даже чтение в памяти может "делать" вещи.

(*) В некоторых компиляторах переменные volatile могут быть добавлены, вычтены, увеличены, уменьшены и т.д. в виде отдельных операций. Это, вероятно, полезно для компилятора:

  volatilevar++;

а

  inc [_volatilevar]

так как последняя форма может быть атомной на многих микропроцессорах (хотя и не на современных многоядерных ПК). Важно отметить, однако, что если утверждение было:

  volatilevar2 = (volatilevar1++);

правильный код не будет:

  mov ax,[_volatilevar1] ; Reads it once
  inc [_volatilevar]     ; Reads it again (oops)
  mov [_volatilevar2],ax

ни

  mov ax,[_volatilevar1]
  mov [_volatilevar2],ax ; Writes in wrong sequence
  inc ax
  mov [_volatilevar1],ax

а скорее

  mov ax,[_volatilevar1]
  mov bx,ax
  inc ax
  mov [_volatilevar1],ax
  mov [_volatilevar2],bx

Написание исходного кода по-разному позволит генерировать более эффективный (и, возможно, безопасный) код. Если "volatilevar1" не прочь прочитать дважды, а "volatilevar2" не прочь написать до volatilevar1, а затем разбить оператор на

  volatilevar2 = volatilevar1;
  volatilevar1++;

позволит быстрее и, возможно, безопаснее, код.

Ответ 8

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

x = y+y+y+y+y;

можно преобразовать в

x = y*5;

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

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