четверг, 27 ноября 2008 г.

Почему гранулярность выделения адресного пространства равна 64 Кб?

Это перевод Why is address space allocation granularity 64K? Автор: Реймонд Чен. Альтернативный перевод.

Возможно, вы задумывались, почему VirtualAlloc выделяет память, выравненную на границу 64 Кб, хотя гранулярность страниц памяти всего 4 Кб.

За это вам нужно поблагодарить процессор Alpha AXP.

У Alpha AXP нет инструкции "загрузить 32-х битное число". Для загрузки 32-х битного целого, вам придётся загрузить два 16-ти битных числа и скомбинировать их.

Поэтому, если бы гранулярность выделения была бы меньше 64 Кб, то любая DLL, которую нужно было бы переместить в памяти, требовала бы двух исправлений на один перемещаемый адрес: одно исправление для верхних 16 бит, а второе - для нижних. А при заёме или переносе между этими половинками всё становится ещё хуже (например, при переносе адреса 4 Кб с $1234F000 на $12350000 происходит обновление как старшей, так и младшей части адреса. Несмотря на то, что перемещение идёт на размер много меньший 64 Кб, оно всё ещё влияет на старшую часть из-за переноса).

Но постойте, это ещё не всё.

Процессор Alpha AXP на самом деле может комбинировать два знаковых 16-ти битных целых для формирования 32-х битного числа. Например, чтобы загрузить значение $1234ABCD, вам сперва нужно использовать инструкцию LDAH для загрузки числа $1235 в верхнюю часть регистра-назначения. Потом вы используете инструкцию LDA для добавления значения -$5433 (поскольку $5433 = $10000 - $ABCD). В результате у вас получится желаемое значение $1234ABCD.
  LDAH t1, $1235(zero)    // t1 = $12350000
  LDA  t1, -$5433(t1)          // t1 = t1 - $5433 = $1234ABCD
Поэтому, если перемещение приводило бы к переносу адреса между "нижней частью" блока в 64 Кб и "верхней частью" (т.е. на размер не кратный 64 Кб), то нужно было бы произвести дополнительные действия, чтобы быть уверенным, что верхняя часть адреса была корректно исправлена.

Самое главное, что компилятор может быть умён и если ему нужно вычислить адреса для двух переменных в одном и том же регионе в 64 Кб, то он использует всего одну инструкцию LDAH для обоих вычислений. Если смещения на размер не кратный 64 Кб были бы разрешены, то компилятор не смог бы применить такую оптимизацию, потому что теперь после перемещения две переменные могли бы оказаться в разных блоках по 64 Кб - а значит для каждой переменной теперь нужна своя индивидуальная инструкция LDAH.

Введение ограничения на гранулярность выделения памяти в 64 Кб решает все эти проблемы.

Если вы были действительно внимательны при чтении, то вы могли уже заметить, почему блок памяти в 64 Кб около границы в 2 Гб является "ничейной землёй". Рассмотрим метод формирования значения $7FFFABCD: поскольку младшие 16 бит лежат в верхней половине 64 Кб, то значение нужно вычислять с помощью вычитания, а не сложения. Наивное решение могло бы быть таким:
  LDAH t1, $8000(zero)      // t1 = $80000000, верно?
  LDA  t1, -$5433(t1)            // t1 = t1 - $5433 = $7FFFABCD, верно?
Только вот это не сработает. +$8000 не влезает в 16-ти битное знаковое целое (максимальное положительное 16-ти битное число - это +$7FFF - прим. пер.), поэтому вам придётся использовать -$8000 - отрицательное число. Кроме того, Alpha AXP - это 64-х битный процессор. Поэтому на самом деле произойдёт:
  LDAH t1, -$8000(zero)     // t1 = $FFFFFFFF`80000000
  LDA  t1, -$5433(t1)            // t1 = t1 - $5433 = $FFFFFFFF`7FFFABCD
Вам нужна ещё третья инструкция, чтобы обнулить старшие 32 бита. Для этого есть остроумный трюк: добавить к регистру ноль и сказать процессору, чтобы он рассматривал результат как 32-х битное число и знаково расширил бы его до 64 бит.
  ADDL t1, zero, t1         // t1 = t1 + 0, с суффиксом L
  // Суффикс L означает знаковое расширение результата с 32 бит до 64
                                 // t1 = $00000000`7FFFABCD
Если бы адреса в районе 64 Кб от границы в 2 Гб были бы разрешены, то к каждому бы вычислению адреса памяти нужно было бы добавлять эту третью инструкцию ADDL на тот случай, если адрес будет перемещён в "опасную зону" около границы в 2 Гб.

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

5 комментариев:

  1. Анонимный20 июня 2011 г., 21:37

    >>в "опасную зону" около границы в 2 Гб
    а как вычислить эту зону?

    ОтветитьУдалить
  2. Ээээ.... прочитайте ещё раз, начиная с "Если вы были действительно внимательны при чтении, то вы могли уже заметить, почему блок памяти в 64 Кб около границы в 2 Гб является "ничейной землёй"." Особенно обращайте внимания на адреса и вычисления.

    ОтветитьУдалить
  3. Анонимный22 июня 2011 г., 23:26

    Прошу прощения, был невнимателен =)
    Александр, большое спасибо.

    ОтветитьУдалить
  4. Долго вникал... Сейчас адреса набираются двумя операциями: сложения и вычитания. А если разрешить использование "опасной зоны", то адреса набирались бы тремя операциями: вычитания, вычитания и обнуления. Лишний такт, это понятно.
    А если не обнулять старшие байты, то число из примера $FFFFFFFF`7FFFABCD процессор будет рассматривать как отрицательное знаковое, верно?

    ОтветитьУдалить
    Ответы
    1. Ну, для сложения с нулём не важно какое это число :) Но да, если рассматривать его как знаковое, то оно будет отрицательным 64-битным числом. Но суть не в том, что оно отрицательное, а в том, что это не тот адрес, который нам нужен. Нам нужен 32-битный адрес 7FFFABCD (или, что то же самое - 000000007FFFABCD).

      Удалить

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

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

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

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

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