Тонкий намёк: к вопросу об оценке производительности оперативной памяти на современных платформах

Одним из процессов, лежащих в основе организации вир­ту­аль­ной памяти, является трансляция ли­ней­но­го ад­ре­са в фи­зи­че­ский. Линейный адрес, подлежащий трансляции, разбивается на поля, при этом 12 младших би­тов ад­ре­су­ют кон­к­рет­ный байт в пределах страницы, объемом 4 килобайта, остальные биты линейного ад­ре­са ис­поль­зу­ют­ся для ин­дек­си­ро­ва­ния таб­лиц страниц, организованных в виде иерархической структуры. Два фун­да­мен­таль­ных документа от Intel де­таль­но опи­сы­ва­ют процесс этой трансляции. Ин­фор­ма­ция в них ак­ту­аль­на и для процессоров AMD.

В основе процедуры лежит такое понятие, как дескриптор, соответствующий каждой странице памяти. Для кэ­ши­ро­ва­ния та­ких деск­рип­то­ров ис­поль­зу­ет­ся буфер ассоциативной трансляции (англ. Translation lookaside buf­fer, TLB). Это сво­е­го рода кэш-память, внутренний ресурс процессора. Подобно кэш-па­мя­ти, TLB мо­жет пе­ре­пол­нять­ся, что про­ис­хо­дит, если количество страниц, с ко­то­ры­ми ра­бо­та­ет про­г­рам­ма, превышает заранее оп­ре­де­лен­ный ли­мит. Пе­ре­пол­не­ние TLB приводит к необходимости подгружать деск­­рип­­то­­ры стра­ниц из DRAM.

В ходе эволюции x86-архитектуры возникли так называемые «большие страницы» или Large Pages. Уловка со­сто­ит в том, чтобы пропустить один из иерархических уровней трансляционных таблиц, при этом адресные биты, со­от­вет­ст­ву­ю­щие это­му уров­ню отнести к полю, выбирающему байт в пределах страницы.

  • В 32-битном режиме на каждом иерархическом уровне для индексирования таблиц используется 10 бит, сле­до­ва­тель­но, разрядность адресного поля страницы возрастет до 12+10=22 бит, размер стра­ни­цы при этом со­став­ля­ет 222 байта, т.е. 4MB.
  • В 64-битном режиме на каждом иерархическом уровне для индексирования таблиц используется 9 бит, сле­до­ва­тель­но, разрядность адресного поля страницы возрастет до 12+9=21 бита, размер стра­ни­цы при этом со­став­ля­ет 221 байта, т.е. 2MB.

Преимущества больших страниц очевидны. Объем памяти, описываемый одним дескриптором, фак­ти­че­ски воз­рас­та­ет на три по­ряд­ка: вместо 4 килобайт — 4 мегабайта (или 2МБ для 64-битного режима). На прак­ти­ке, в боль­шин­с­т­ве слу­ча­ев это позволяет избежать переполнения буфера ассоциативной трансляции, кро­ме того, длина ие­рар­хи­че­ской це­поч­ки таб­лиц умень­ша­ет­ся на единицу. Оба этих фактора упрощают про­це­ду­ру вычисления фи­зи­че­ско­го ад­ре­са, спо­соб­ст­вуя сни­же­нию ла­тент­но­с­ти. Недостатки — ухудшение ма­нев­рен­но­с­ти при выделении памяти и неэкономное ее рас­хо­до­ва­ние, по­сколь­ку ба­зо­вый ад­рес и объем вы­де­ля­е­мо­го блока становятся кратными размеру большой стра­ни­цы.

Большие страницы и латентность

Ситуация, при которой дескриптор страницы отсутствует в TLB и требуется дополнительное время для его за­­груз­­ки из опе­ра­тив­ной памяти, называется TLB-промахом (TLB miss). Если дескриптор находится в TLB, то имеет мес­то TLB hit — это гарантирует минимальную латентность при доступе к запрошенным данным.

Не требует доказательств тот факт, что использование больших страниц позволяет предотвращать пе­ре­пол­не­ние бу­фе­ра TLB, что снижает вероятность воз­ник­но­ве­ния си­ту­а­ции TLB miss.

Интерференция Large Pages и Prefetch

С появлением функционального расширения SSE в наборе инструкций x86 появились так называемые Prefetch Hints, это сво­е­об­раз­ные на­ме­ки, по­зво­ля­ю­щие про­г­рам­мно­му обес­пе­че­нию явным образом за­бла­го­вре­мен­но ин­фор­ми­ро­вать про­цес­сор о предстоящих операциях доступа к памяти. В отличие от клас­си­че­ских ин­ст­рук­ций, вы­пол­не­ние ко­то­рых строго де­тер­ми­ни­ро­ва­но про­цес­сор­ной ар­хи­тек­ту­рой, отработка раз­лич­ны­ми про­цес­со­ра­ми Prefetch hints и дру­гих ви­дов «на­ме­ков» носит Im­ple­men­ta­tion Spe­ci­fic характер, то есть за­ви­сит от модели CPU и кон­тек­с­та вы­пол­не­ния.

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

Что в итоге?

В итоге, создавая тестовую среду для оценки про­из­во­ди­тель­но­с­ти ОЗУ, необходимо учитывать вза­и­мо­вли­я­ние спо­со­бов ад­ре­са­ции вир­ту­аль­ной памяти, фактора TLB и тех спекулятивных методов («намеков»), ко­то­рые в архитектуре конкретного CPU могут определять такие кардинальные параметры платформы, как ла­тен­т­ность доступа и про­пуск­ная спо­соб­ность DRAM.

Опираясь на Implementation-Specific особенности процессора, в частности используя Prefetch hints, сле­ду­ет учи­ты­вать, что по­ве­де­ние TLB при спекулятивной загрузке может зависеть не только от модели процессора, но и от со­дер­жи­мо­го Model-Specific регистров, загруженного блока микрокодов и ряда других факторов.

К истории вопроса

Согласно документации Intel процессоры P4 семейства Willamette и Northwood игнорируют Prefetch hint, если его отработка при­ве­дет к TLB miss, поэтому для них влияние Large Pages достаточно выражено не только в тес­тах ла­тент­но­с­ти, но и про­пуск­ной спо­соб­но­с­ти DRAM. Это результат того, что при увеличении размера стра­ни­цы с 4KB до 4MB ко­ли­че­с­тво барь­е­ров, рас­по­ло­жен­ных на стыках страниц и пре­пят­ст­ву­ю­щих опе­ре­жа­ю­ще­му чте­нию, дра­ма­ти­че­ски умень­ша­ет­ся.

У предшественников Prescott отсутствие дескриптора страницы в буфере ассоциативной трансляции (TLB miss) ос­та­нав­ли­ва­ет работу механизма опережающей загрузки — Prefetch. Именно TLB-промах создает сво­е­об­раз­ный барьер, который не может преодолеть спекулятивная загрузка, и который преодолевается толь­ко в хо­де явного обращения к операнду, а значит скорость падает. Большие страницы, снижая ко­ли­че­с­т­во TLB-про­ма­хов, уст­ра­ня­ют дан­ные барь­е­ры, поэтому при их ис­поль­зо­ва­нии растет пропускная спо­соб­ность па­мя­ти.

В Prescott такой барьер устранен, процессоры этого и последующих поколений способны за­гру­зить TLB в рам­ках опе­ре­жа­ю­щей вы­бор­ки, не ожидая явного об­ра­ще­ния к операнду, когда прятать ла­тент­ность уже позд­­но — их архитектура предполагает обработку TLB-промахов и загрузку дескрипторов страниц и в ходе спе­ку­ля­тив­ных сценариев. В со­вре­мен­ных про­цес­со­рах изменение состояния TLB также происходит под вли­я­ни­ем опе­ре­жа­ю­ще­го вы­пол­не­ния фраг­мен­та про­г­рам­мно­го кода, который на самом деле, согласно за­кон­ной логике программы, не должен быть выполнен. Спо­­соб­­ность из­ме­нять со­дер­жи­мое буфера TLB в рам­ках спе­ку­ля­тив­но­го выполнения в дальнейшем сы­гра­ла злую шут­ку, став одной из составляющих так на­зы­ва­е­мых мик­ро­ар­хи­тек­тур­ных уяз­ви­мо­стей.

Литература и ссылки

  1. IA-32 Intel Architecture Optimization, Reference Manual
  2. TLBs, Paging-Structure Caches, and Their Invalidation
  3. 5-Level Paging and 5-Level EPT