Разрешена оптимизация компилятора C # для локальных переменных и восстановление значения из памяти - PullRequest
11 голосов
/ 05 октября 2011

РЕДАКТИРОВАТЬ : Я спрашиваю, что происходит, когда два потока одновременно обращаются к одним и тем же данным без надлежащей синхронизации (до этого редактирования эта точка не была четко выражена).

У меня есть вопрос об оптимизации, выполняемой компилятором C # и JIT-компилятором.

Рассмотрим следующий упрощенный пример:

class Example {
    private Action _action;

    private void InvokeAction() {
        var local = this._action;
        if (local != null) {
            local();
        }
    }
}

Пожалуйста, игнорируйте в примере, что чтение _action может привести к кэшированному и устаревшему значению, поскольку нет ни volatile-спецификатора, ни какой-либо другой синхронизации. Дело не в этом :)

Разрешено ли компилятору (или фактически джиттеру во время выполнения) оптимизировать присвоение локальной переменной и вместо этого читать _action из памяти дважды:

class Example {
    private Action _action;

    private void InvokeAction() {
        if (this._action != null) {
            this._action(); // might be set to null by an other thread.
        }
    }
}

, который может выдать NullReferenceException, если для поля _action установлено одновременное присваивание null.

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

Ответы [ 3 ]

7 голосов
/ 07 октября 2011

Я скажу (частично) противоположность mgronber :-) А-а-а-а ... В конце я говорю то же самое ... Только то, что я цитирую статью :-( Я дам ему + 1.

Это ЮРИДИЧЕСКАЯ оптимизация в соответствии со спецификациями ECMA, но это недопустимая оптимизация в соответствии с .NET> = 2.0 «спецификациями».

Из Понимание влияния низкоблокирующих технологий в многопоточных приложениях

Читайте здесь Сильная Модель 2: .NET Framework 2.0

Точка 2:

Чтение и запись не могут быть введены.

Объяснение ниже:

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

НО примечание на той же странице, под Техника 1: Избегание блокировок при некоторых чтениях

В системах, использующих модель ECMA, есть дополнительная тонкость. Четное если только одна ячейка памяти извлекается в локальную переменную, и это local используется несколько раз, каждое использование может иметь разные значения! Это связано с тем, что модель ECMA позволяет компилятору исключать локальные переменная и повторно выбрать местоположение при каждом использовании. Если обновления происходя одновременно, каждая выборка может иметь различное значение. это поведение может быть подавлено изменчивыми декларациями, но проблема легко пропустить.

Если вы пишете под Mono, вам следует сообщить, что по крайней мере до 2008 года он работал над моделью памяти ECMA (или так они писали в своем списке рассылки )

2 голосов
/ 05 октября 2011

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

Однако я думаю, что текущие реализации Microsoft CLR не оптимизируют локальные переменные.

0 голосов
/ 28 ноября 2017

С C # 7 вы должны написать пример следующим образом, и фактически IDE предложит его как «упрощение» для вас. Компилятор сгенерирует код, который использует временный локальный код только для чтения местоположения _action из основной памяти один раз (независимо от того, является ли он null или нет), и это помогает предотвратить общую гонку, показанную Второй пример OP, т. Е. Где _action доступен дважды и может быть установлен на null другим потоком между ними.

class Example
{
    private Action _action;

    private void InvokeAction()
    {
        this._action?.Invoke();
    }
}
...