Анализ выходных данных x86, сгенерированных JIT в контексте volatile

Я пишу этот пост в связи с глубоким пониманием изменчивости в Java

public class Main {
    private int x;
    private volatile int g;


    public void actor1(){
       x = 1;
       g = 1;
    }


    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

Теперь я анализирую, что JIT сгенерировал для приведенного выше фрагмента кода. Из нашего обсуждения в моем предыдущем посте мы знаем, что вывод 1, 0 невозможно, потому что:


написать в volatile v вызывает, что каждое действие a предшествующего v вызывает это a будет виден (будет сброшен в память) до v будет видно


   .................(I removed not important body of method).....

  0x00007f42307d9d5e: c7460c01000000     (1) mov       dword ptr [rsi+0ch],1h
                                                ;*putfield x
                                                ; - package.Main::actor1@2 (line 14)

  0x00007f42307d9d65: bf01000000          (2) mov       edi,1h
  0x00007f42307d9d6a: 897e10              (3) mov       dword ptr [rsi+10h],edi
  0x00007f42307d9d6d: f083042400          (4) lock add  dword ptr [rsp],0h
                                                ;*putfield g
                                                ; - package.Main::actor1@7 (line 15)

  0x00007f42307d9d72: 4883c430            add       rsp,30h
  0x00007f42307d9d76: 5d                  pop       rbp
  0x00007f42307d9d77: 850583535116        test      dword ptr [7f4246cef100h],eax
                                                ;   {poll_return}
  0x00007f42307d9d7d: c3                  ret

Правильно ли я понимаю, что это работает, потому что x86 не может сделать StoreStore изменение порядка? Если бы это было возможно, потребовался бы дополнительный барьер памяти, да?


Отредактировано после ОТЛИЧНО @ Ответ Евгения:

 int tmp = i; // volatile load
 // [LoadStore]
 // [LoadLoad]

Вот, я понимаю, что вы имеете в виду - понятно every action below (after) изменчивое чтение (int tmp = i) не будет переупорядочено.

 // [StoreLoad] -- this one
 int tmp = i; // volatile load
 // [LoadStore]
 // [LoadLoad]

Здесь вы ставите еще один барьер. Это гарантирует нам, что никакие действия не будут переупорядочены int tmp = i, Но почему это важно? Почему у меня есть сомнения? Из того, что я знаю volatile load гарантии:

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

Я вижу, ты пишешь:

Должна быть последовательная последовательность

Но я не могу понять, почему требуется последовательная согласованность.

1 ответ

Решение

Сначала пара вещей will be flushed to memory - это довольно ошибочно. Он почти никогда не сбрасывается в основную память - он обычно истощает StoreBuffer для L1 и это зависит от протокола когерентности кеша, чтобы синхронизировать данные между всеми кешами, но если вам легче понять эту концепцию в этих терминах, это хорошо - просто знайте, что это немного отличается и быстрее.

Это хороший вопрос, почему [StoreLoad] действительно ли, возможно, это немного прояснит ситуацию. volatile на самом деле все о заборах, и вот пример того, какие барьеры будут вставлены в случае некоторых нестабильных операций. Например, у нас есть volatile load:

  // i is some shared volatile field
  int tmp = i; // volatile load of "i"
  // [LoadLoad|LoadStore]

Обратите внимание на два барьера здесь LoadStore а также LoadLoad; в простом английском это означает, что любой Load а также Store которые идут после volatile load/read Нельзя "сдвинуть" барьер вверх, их нельзя переупорядочить "выше" этой изменчивой нагрузки.

А вот пример для volatile store,

 // "i" is a shared volatile variable
 // [StoreStore|LoadStore]
 i = tmp; // volatile store

Это означает, что любой Load а также Store не может идти "ниже" самого магазина загрузки.

Это в основном строит отношения до того, volatile load быть приобретающей нагрузкой и volatile store быть магазином выпуска (это также связано с тем, как Store а также Load Буферы процессора реализованы, но это в значительной степени выходит за рамки вопроса).

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

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

 // any store of some other volatile
 // can not be reordered with this volatile load
 // [StoreLoad] -- this one
 int tmp = i; // volatile load of a shared variable "i"
 // [LoadStore|LoadLoad]

И еще один здесь:

// [StoreStore|LoadStore]
i = tmp; // volatile store
// [StoreLoad] -- and this one

Теперь оказывается, что на x86 3 из 4 барьеров памяти свободны - так как это strong memory model, Единственное, что должно быть реализовано, это StoreLoad, На других процессорах, как ARM например, lwsycn одна инструкция используется, но я не знаю много о них.

Обычно mfence это хороший вариант для StoreLoad на x86, но то же самое гарантируется через lock add (AFAIK дешевле), поэтому вы видите это там. В основном это StoreLoad барьер. И да - вы правы в своем последнем предложении, для более слабой модели памяти - StoreStore барьер будет необходимо. Кстати, это то, что используется, когда вы безопасно публикуете ссылку через final поля внутри конструктора. После выхода из конструктора вставляются два забора: LoadStore а также StoreStore,

Возьмите все это с недоверием - JVM может игнорировать их, если не нарушает никаких правил: Алексей Шипилев отлично об этом говорит.


РЕДАКТИРОВАТЬ

Предположим, у вас есть этот случай:

[StoreStore|LoadStore]
int x = 4; // volatile store of a shared "x" variable

int y = 3; // non-volatile store of shared variable "y"

int z = x; // volatile load
[LoadLoad|LoadStore]

В основном нет барьера, который бы помешал volatile store переупорядочить с volatile load (т. е. сначала будет выполняться изменчивая нагрузка), и это, очевидно, вызовет проблемы; последовательная последовательность, таким образом, нарушается.

Вы как бы упускаете суть здесь, кстати (если я не ошибаюсь) через Every action after volatile load won't be reordered before volatile load is visible, Переупорядочение невозможно с самой энергозависимой - другие операции могут быть переупорядочены. Позволь мне привести пример:

 int tmp = i; // volatile load of a shared variable "i"
 // [LoadStore|LoadLoad]

 int x = 3; // plain store
 int y = 4; // plain store

Последние две операции x = 3 а также y = 4 абсолютно свободны, чтобы быть переупорядоченными, они не могут парить над изменчивыми, но они могут быть переупорядочены через себя. Приведенный выше пример будет совершенно законным:

 int tmp = i; // volatile load
 // [LoadStore|LoadLoad]

 // see how they have been inverted here...
 int y = 4; // plain store
 int x = 3; // plain store
Другие вопросы по тегам