.NET JIT компилятор изменчивых оптимизаций - PullRequest
0 голосов
/ 01 октября 2018

https://msdn.microsoft.com/en-us/magazine/jj883956.aspx

Рассмотрим шаблон цикла опроса:

private bool _flag = true; 
public void Run() 
{
    // Set _flag to false on another thread
    new Thread(() => { _flag = false; }).Start();
    // Poll the _flag field until it is set to false
    while (_flag) ;
    // The loop might never terminate! 
} 

В этом случае JIT-компилятор .NET 4.5 может переписать цикл следующим образом:

if (_flag) { while (true); } 

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

Обратите внимание, что если бы поле _flag было энергозависимым, JIT-компилятор не выводил бы чтение из цикла.(Более подробное объяснение этого шаблона см. В разделе «Цикл опроса» в статье за ​​декабрь.)

Будет ли JIT-компилятор по-прежнему оптимизировать код, как показано выше, если я заблокирую _flag илитолько заставив его volatile остановить оптимизацию?

Эрик Липперт может сказать о волатильности следующее:

Честно говоря, я отговариваю вас от создания волатильного поля.Изменчивые поля являются признаком того, что вы делаете что-то совершенно безумное: вы пытаетесь прочитать и записать одно и то же значение в двух разных потоках, не устанавливая блокировку на месте.Блокировки гарантируют, что считывание или изменение памяти внутри блокировки считается согласованным, блокировки гарантируют, что только один поток одновременно обращается к данному фрагменту памяти, и так далее.Количество ситуаций, в которых блокировка слишком медленная, очень мало, и вероятность того, что вы ошибетесь в коде, потому что не понимаете точную модель памяти, очень велика.Я не пытаюсь писать какой-либо код с низкой блокировкой, за исключением самых тривиальных операций с блокировкой.Я оставляю использование «volatile» реальным экспертам.

Подводя итог: Кто гарантирует, что упомянутая выше оптимизация не разрушит мой код? Only volatile?Также заявление lock?Или что-то еще?

Поскольку Эрик Липперт отговаривает вас от использования volatile, должно быть что-то еще?


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


Переменная bool не является примитивом синхронизации потоков: Вопрос подразумевается какобщий вопрос.Когда компилятор не выполнит оптимизацию?


Dupilcate: Этот вопрос явно касается оптимизации.Тот, на который вы ссылаетесь, не упоминает оптимизации.

Ответы [ 4 ]

0 голосов
/ 08 октября 2018

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

Хотя, конечно, это ложная языковая особенность, поскольку в C / C ++ есть другая семантика, и это то, что большинство программистов уже могли получитьиспользовал к.Поэтому использование «volatile» для C # и Windows (за исключением архитектуры ARM) иногда сбивает с толку.

0 голосов
/ 01 октября 2018

Когда вы используете lock или Interlocked Operations , вы сообщаете компилятору, что в блоке или ячейке памяти есть гонки данных и что он не может делать предположения о доступе к этим папкам.Таким образом, компилятор отказывается от оптимизаций, которые он мог бы выполнить в среде, свободной от гонки данных.Этот подразумеваемый контракт также означает, что вы сообщаете компилятору, что вы будете обращаться к этим расположениям соответствующим образом без каких-либо гонок.

0 голосов
/ 01 октября 2018

Давайте ответим на заданный вопрос:

Будет ли JIT-компилятор по-прежнему оптимизировать код, как показано выше, если я заблокирую _flag или только сделаю его изменчивым, остановит оптимизацию?

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

Будет ли JIT-компилятор по-прежнему оптимизировать код, как показано выше, если я заблокирую _flag?

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

private bool _flag = true; 
private object _flagLock = new object();
public void Run() 
{
  new Thread(() => { lock(_flaglock) _flag = false; }).Start();
  while (true)
    lock (_flaglock)
      if (!_flag)
        break;
} 

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

Вы сказали, что замки были сильнее летучих;что это значит?

Чтение в летучие компоненты предотвращает перемещение определенных операций во времени.Запись в volatiles предотвращает перемещение определенных операций во времени.Блокировки препятствуют своевременному перемещению больше операций.Эта семантика предотвращения называется «ограждением памяти» - в основном, летучие вещества вводят половину ограждения, замки вводят полное ограждение.

Подробнее см. Раздел спецификации C # по специальным побочным эффектам.

Как всегда, я напомню, что летучие вещества не дают глобальных гарантий свежести .В многопоточном программировании на C # нет такой вещи, как «последнее» значение переменной, и поэтому изменчивые чтения не дают вам «последнего» значения, потому что его не существует.Идея, что существует «последнее» значение, подразумевает, что чтение и запись всегда имеют глобально согласованный порядок во времени, и это неверно.Потоки могут по-прежнему расходиться во мнениях относительно порядка изменчивых операций чтения и записи.

Блокировки не позволяют выполнить эту оптимизацию;летучие вещества препятствуют этой оптимизации.Это единственное, что мешает оптимизации?

Нет.Вы также можете использовать операции Interlocked или явно вводить ограничения памяти.

Я понимаю, достаточно ли этого для правильного использования volatile?

Нет.

Что мне делать?

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

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

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

0 голосов
/ 01 октября 2018

Этот вопрос явно касается оптимизации

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


Вы не можете заблокировать _flag.lock - это синтаксис для класса Monitor.Этот класс блокируется на основе объекта в куче._flag - это bool, который не блокируется.

Чтобы отменить цикл, я бы использовал CancellationTokenSource для этого в эти дни.Он использует изменчивый доступ внутри, но скрывает это от вас.Цикл опрашивает CancellationToken, и отмена цикла выполняется путем вызова CancellationTokenSource.Cancel().Это очень самодокументируемый и простой в реализации.

Вы также можете свернуть любой доступ к _flag в замке.Это будет выглядеть так:

object lockObj = new object(); //need any heap object to lock
...

while (true) {
 lock (lockObj) {
  if (_flag) break;
 }
 ...
}

...

lock (lockObj) _flag = true;

Вы также можете использовать volatile.Эрик Липперт совершенно прав, что лучше не прикасаться к многопоточности, если не нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...