понедельник, 21 января 2019 г.

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

Это перевод Why does the compiler generate memory operations on the full variable even though only one byte is involved? Автор: Реймонд Чен.

Когда-то я помогал с генерацией кода JIT компиляторе, и я заметил одну вещь: когда компилятору нужно было, скажем, установить старший бит в четырёхбайтовой переменной, он сделал так:
xor dword ptr [variable], 80000000h
вместо более компактного:
xor byte ptr [variable + 3], 80h
Эти две операции функционально эквивалентны: установка верхнего бита в четырёхбайтовом значении эквивалентна установке верхнего бита в однобайтовом значении - потому что младшие биты не зависят от операции.

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

Ответ заключается в ещё одной из скрытых переменных внутри процессора. Она называется буфер хранилища (store buffer) и используется в процессе, называемом пересылкой из хранилища в загрузку (store-to-load forwarding). Вы можете прочитать больше здесь, но короткая версия состоит в том, что, когда спекулятивное выполнение встречает запись в память (операцию сохранения, store), оно не может изменить память немедленно, потому что это просто спекуляция, а не выполнение. Вместо этого оно записывает значение в буфер хранилища, который запоминает: "если мы в конечном итоге реализуем это предположение, то нам нужно записать значение V в адрес A".

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

Конечно, жизнь не так проста, как кажется, потому что есть много способов изменить память по адресу A - благодаря тому, что x86 разрешает как доступ к памяти через старшее и младшее слова, так и доступ к памяти со смещением (без выравнивания). Обращение к памяти без выравнивания означает, что если вы хотите прочитать четырёхбайтовое значение из A, вы должны искать в хранилище не только четырёхбайтовые записи в A, но также и четырёхбайтовые записи в диапазоне от A - 3 до A + 3, потому что они перекрывают память, которую вы собираетесь прочитать. А доступ к памяти старшие/младшие части означает, что вам также нужно искать однобайтовые записи в диапазоне от A до A + 3, а также двухбайтовые записи в диапазоне от A - 1 до A + 3 (и даже больше комбинаций - как только вы добавите в рассмотрение SIMD регистры).

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

На практике x86 не заботится о такой сложной реконструкции. Когда он обнаруживает, что между буфером хранилища и предполагаемой загрузкой существует сложное взаимодействие, он приостанавливает (stall) пересылку из хранилища в загрузку.

Я не знаю, насколько серьёзна эта задержка, но понятно, что вы бы не хотели, чтобы она возникала. Вот почему JIT компилятор, над которым я работал, пытается получить доступ к каждой переменной однотипным образом (четырёхбайтовые переменные с четырёхбайтовыми инструкциями и т.д.) - таким образом, эти задержки не возникают.

3 комментария:

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

    >буфер хранилища
    Буфер записей (store, операций записи), соответственно, store-to-load forwarding — подстановка спекулятивно записанных данных при спекулятивном чтении.

    ОтветитьУдалить
    Ответы
    1. Во, мне первый вариант нравится, я бы подставил, но второй получается какой-то монстр.

      Удалить
    2. Подстановка записей в чтения!

      Удалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.