При каких обстоятельствах большие страницы могут привести к ускорению?

Современные процессоры x86 имеют возможность поддерживать страницы большего размера, чем унаследованные 4 КБ (т. Е. 2 ​​МБ или 4 МБ), и есть средства ОС ( Linux, Windows) для доступа к этой функции.

Ссылка Microsoft, приведенная выше, гласит, что большие страницы "повышают эффективность буфера перевода, что может повысить производительность часто используемой памяти". Что не очень помогает прогнозировать, улучшат ли большие страницы какую-либо ситуацию. Меня интересуют конкретные, предпочтительно количественные примеры того, как перемещение некоторой программной логики (или целого приложения) на использование огромных страниц привело к некоторому улучшению производительности. У кого-нибудь есть истории успеха?

Есть один конкретный случай, который я знаю о себе: использование огромных страниц может значительно сократить время, необходимое для разветвления большого процесса (предположительно, поскольку число записей TLB, нуждающихся в копировании, уменьшается в 1000 раз). Меня интересует, могут ли большие страницы быть полезными в менее экзотических сценариях.

5 ответов

Решение

Я попытался придумать некоторый код, который позволил бы максимизировать работу TLB с 4 тыс. Страниц, чтобы изучить возможные выгоды от больших страниц. Приведенный ниже материал работает в 2,6 раза быстрее (чем страницы по 4 КБ), когда 2-мегабайтные страницы предоставлены malloc libhugetlbfs (Intel i7, 64-битный Debian Lenny); надеюсь, очевидно, что scoped_timer а также random0n делать.

  volatile char force_result;

  const size_t mb=512;
  const size_t stride=4096;
  std::vector<char> src(mb<<20,0xff);
  std::vector<size_t> idx;
  for (size_t i=0;i<src.size();i+=stride) idx.push_back(i);
  random0n r0n(/*seed=*/23);
  std::random_shuffle(idx.begin(),idx.end(),r0n);

  {
    scoped_timer t
      ("TLB thrash random",mb/static_cast<float>(stride),"MegaAccess");
    char hash=0;
    for (size_t i=0;i<idx.size();++i) 
      hash=(hash^src[idx[i]]);
    force_result=hash;
  }

Более простая версия "прямой линии" с просто hash=hash^src[i] только выиграл 16% от больших страниц, но (дикое предположение) необычное аппаратное обеспечение предварительной выборки Intel может помочь в случае 4K, когда доступ предсказуем (я полагаю, я мог бы отключить предварительную выборку, чтобы выяснить, так ли это).

Наибольшее различие в производительности произойдет, когда вы будете делать произвольные обращения с большим интервалом к ​​большой области памяти, где "большой" означает гораздо больший, чем диапазон, который может отображаться всеми небольшими записями страниц в TLB (которые обычно иметь несколько уровней в современных процессорах).

Чтобы усложнить задачу, количество записей TLB для страниц размером 4 КБ часто превышает количество записей для страниц размером 2 МБ, но это сильно зависит от процессора. Существует также много вариантов того, сколько записей "больших страниц" доступно в TLB 2-го уровня.

Например, в системе AMD Opteron Family 10h Revision D ("Стамбул") cpuid сообщает:

  • L1 DTLB: 4 КБ страниц: 48 записей; 2 МБ страниц: 48 записей; 1 ГБ страниц: 48 записей
  • L2 TLB: страницы 4 КБ: 512 записей; 2 МБ страниц: 128 записей; 1 ГБ страниц: 16 записей

Находясь в системе Intel Xeon 56xx ("Westmere"), cpuid сообщает:

  • L1 DTLB: 4 КБ страниц: 64 записи; 2MB страниц: 32 записи
  • L2 TLB: страницы 4 КБ: 512 записей; 2 МБ страниц: нет

Оба могут отобразить 2 МБ (512*4 КБ), используя небольшие страницы, прежде чем потеряет TLB уровня 2, в то время как система Westmere может отобразить 64 МБ, используя 32 записи TLB 2 МБ, а система AMD может отобразить 352 МБ, используя 176 записей TLB 2 МБ в своих L1 и L2. Буферы TLB. Любая система получит значительное ускорение за счет использования больших страниц для произвольного доступа к диапазонам памяти, которые намного больше, чем 2 МБ и меньше, чем 64 МБ. Система AMD должна продолжать показывать хорошую производительность, используя большие страницы для гораздо большего диапазона памяти.

Во всех этих случаях вы пытаетесь избежать наихудшего (примечание 1) сценария обхода всех четырех уровней иерархической трансляции адресов x86_64.
Если ни один из механизмов кэширования преобразования адресов (примечание 2) не работает, для этого необходимо:

  • 5 поездок в память для загрузки данных, отображаемых на странице 4 КБ,
  • 4 поездки в память для загрузки данных, отображаемых на странице размером 2 МБ, и
  • 3 поездки в память для загрузки данных, отображаемых на странице объемом 1 ГБ.

В каждом случае последняя поездка в память должна получить запрошенные данные, в то время как другие поездки необходимы, чтобы получить различные части информации о переводе страницы. Лучшее описание, которое я видел, приведено в разделе 5.3 "Руководства по программированию для архитектуры AMD64, том 2: системное программирование" (публикация 24593) http://support.amd.com/us/Embedded_TechDocs/24593.pdf

Примечание 1: приведенные выше цифры не являются на самом деле худшим случаем. Запуск под виртуальной машиной ухудшает эти показатели. Работа в среде, которая приводит к тому, что память, содержащая различные уровни таблиц страниц, переключается на диск, значительно ухудшает производительность.

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

Это становится все более эзотерическим, но огромные страницы TLB существенно влияют на архитектуру Intel Xeon Phi (MIC) при передаче памяти DMA (от хоста к Phi через PCIe). Эта ссылка Intel описывает, как включить огромные страницы. Я обнаружил, что увеличение размера передачи DMA свыше 8 МБ при нормальном размере страницы TLB (4 КБ) начало снижать производительность, с примерно 3 ГБ / с до менее 1 ГБ / с, когда размер передачи достиг 512 МБ.

После включения огромных страниц TLB (2 МБ) скорость передачи данных продолжала увеличиваться до более 5 ГБ / с для передач DMA 512 МБ.

Я видел улучшение в некоторых сценариях HPC/Grid - в частности, физические пакеты с очень, очень большими моделями на машинах с большим и большим количеством оперативной памяти. Кроме того, процесс запуска модели был единственным активным на машине. Я подозреваю, хотя и не измерил, что определенные функции БД (например, массовый импорт) также выиграют.

Лично я думаю, что если у вас нет очень хорошо профилированного / понятного профиля доступа к памяти и он обеспечивает большой доступ к памяти, маловероятно, что вы увидите какое-либо существенное улучшение.

Я получаю ускорение на ~5% на серверах с большим объемом памяти (>=64 ГБ), на которых выполняются большие процессы. например, для процесса Java объемом 16 ГБ, это страницы 4M x 4 КБ, но только страницы 4K x 4 МБ.

Другие вопросы по тегам