Предусматривает ли предварительная выборка программного обеспечения буфер заполнения строки (LFB)? - программирование
Подтвердить что ты не робот

Предусматривает ли предварительная выборка программного обеспечения буфер заполнения строки (LFB)?

Я понял, что Little Law ограничивает скорость передачи данных при заданной задержке и заданном уровне concurrency. Если вы хотите перевести что-то быстрее, вам либо нужны большие трансферы, либо больше передач "в полете", либо более низкая латентность. В случае чтения из ОЗУ concurrency ограничено количеством буферов заполнения строк.

Буфер заполнения строки выделяется, когда загрузка пропускает кеш L1. Современные чипы Intel (Nehalem, Sandy Bridge, Ivy Bridge, Haswell) имеют 10 LFB на ядро ​​и, таким образом, ограничены 10 выдающимися промахами кэша на ядро. Если время ожидания лазера составляет 70 нс (правдоподобно), и каждая передача составляет 128 байт (линия кэша 64B плюс его предварительно настроенное двойное соединение), это ограничивает пропускную способность на ядро ​​до: 10 * 128B/75 ns = ~ 16 ГБ/с. Тесты, такие как однопоточные Stream, подтверждают, что это достаточно точно.

Очевидным способом уменьшить задержку будет предварительная выборка желаемых данных с помощью инструкций x64, таких как PREFETCHT0, PREFETCHT1, PREFETCHT2 или PREFETCHNTA, так что ее не нужно читать из ОЗУ. Но я не смог что-то ускорить, используя их. Кажется, проблема заключается в том, что сами команды __mm_prefetch() потребляют LFB, поэтому они тоже подвержены тем же пределам. Предварительные настройки оборудования не касаются LFB, но также не будут пересекать границы страниц.

Но я не могу найти нигде этого документально. Самое близкое, что я нашел, - это 15-летняя статья , в которой говорится, что prefetch на Pentium III использует Buffer Fill Buffers. Я беспокоюсь, что с тех пор ситуация изменилась. И так как я думаю, что LFB связаны с кешем L1, я не уверен, почему их будет использовать prefetch для L2 или L3. И тем не менее, скорости, которые я измеряю, согласуются с этим.

Итак: есть ли способ инициировать выборку из нового места в памяти, не используя один из этих 10 буферов линейной заливки, тем самым достигая более высокой пропускной способности, обойдя вокруг Little Law?

4b9b3361

Ответ 1

Прежде всего, небольшая коррекция - прочитайте руководство по оптимизации, и вы заметите, что некоторые превенторы HW принадлежат к кэшу L2 и как таковые не ограничены количеством буферов заполнения, а скорее аналогом L2.

"Пространственный предварительный выборщик" (выделенная ячейка coloated-64B, которую вы составляете, до 128B) является одним из них, поэтому теоретически, если вы выберете каждую другую линию, вы сможете получить более высокую пропускную способность (некоторые предварительные выборки DCU может попытаться "заполнить пробелы для вас", но теоретически они должны иметь более низкий приоритет, чтобы он мог работать).

Тем не менее, "король" prefetcher является другим парнем, "L2 стримера". В разделе 2.1.5.4 говорится:

Streamer: этот prefetcher контролирует запросы чтения из кеша L1 для восходящих и нисходящих последовательностей адресов. Контролируемые запросы на чтение включают в себя запросы Lache DCache, инициированные операциями загрузки и хранения, а также аппаратные предварительные считыватели и запросы Lache ICache для извлечения кода. Когда обнаружен прямой или обратный поток запросов, ожидаемые строки кэша предварительно заполняются. Предварительно запрограммированные строки кэша должны быть на одной странице 4K

Важная часть -

Поток может выдавать два запроса предварительной выборки при каждом поиске L2. Стример   может работать до 20 строк перед загрузкой

Это соотношение 2: 1 означает, что для потока доступа, который распознается этим префишером, он всегда будет работать впереди ваших обращений. Это правда, что вы не увидите эти строки в своем L1 автоматически, но это означает, что если все работает хорошо, вы всегда должны получать L2 задержка для них (как только поток предварительной выборки имел достаточно времени для запуска и уменьшения L3/памяти задержки). У вас может быть только 10 LFB, но, как вы отметили в своих расчетах - чем короче будет латентность доступа, тем быстрее вы сможете заменить их более высокой пропускной способностью, которую вы можете достичь. Это существенно сокращает задержку L1 <-- mem в параллельных потоках L1 <-- L2 и L2 <-- mem.

Что касается вопроса в заголовке - разумно предположить, что для prefetches, пытающегося заполнить L1, требуется буфер заполнения строки для хранения извлеченных данных для этого уровня. Вероятно, это должно включать все предварительные выборки L1. Что касается предварительных выборок SW, в разделе 7.4.3 говорится:

Бывают случаи, когда PREFETCH не выполняет предварительную выборку данных. К ним относятся:

     
  •   
  • PREFETCH вызывает отправку DTLB (буфера просмотра данных). Это относится к процессорам Pentium 4 с сигнатурой CPUID, соответствующей семейству 15, модели 0, 1 или 2. PREFETCH разрешает пропуски DTLB и извлекает данные на процессорах Pentium 4 с сигнатурой CPUID, соответствующей семейству 15, модель 3.  
  • Доступ к указанному адресу, который вызывает ошибку/исключение.  
  • Если подсистема памяти заканчивается буферами запросов между кешем первого уровня и кэшем второго уровня.  
     

...

Итак, я предполагаю, что вы правы, а предварительные выборки SW - это не способ искусственного увеличения количества невыполненных запросов. Однако одно и то же объяснение здесь применимо - если вы знаете, как использовать предварительную выборку ПО для доступа к вашим линиям достаточно хорошо, вы можете уменьшить некоторую задержку доступа и увеличить эффективную BW. Однако это не будет работать для длинных потоков по двум причинам: 1) емкость вашего кеша ограничена (даже если предварительная выборка является временной, например, t0-аромат), и 2) вам все равно нужно оплатить полную задержку L1 → mem для каждая предварительная выборка, так что вы просто немного движетесь со своим напряжением - если ваши манипуляции с данными быстрее, чем доступ к памяти, вы в конечном итоге наверстаете у вас предварительную выборку. Таким образом, это работает только в том случае, если вы можете предварительно предварительно собрать все, что вам нужно, и сохранить их там.

Ответ 2

Основываясь на моем тестировании, все типы команд предварительной выборки потребляют буферы заполнения строки.

В частности, Я добавил некоторые тесты нагрузки и предварительной выборки на uarch-bench, которые используют большие нагрузки над буферами разных размеров. Вот типичные результаты на моем Skylake i7-6700HQ:

                     Benchmark   Cycles    Nanos
  16-KiB parallel        loads     0.50     0.19
  16-KiB parallel   prefetcht0     0.50     0.19
  16-KiB parallel   prefetcht1     1.15     0.44
  16-KiB parallel   prefetcht2     1.24     0.48
  16-KiB parallel prefetchtnta     0.50     0.19

  32-KiB parallel        loads     0.50     0.19
  32-KiB parallel   prefetcht0     0.50     0.19
  32-KiB parallel   prefetcht1     1.28     0.49
  32-KiB parallel   prefetcht2     1.28     0.49
  32-KiB parallel prefetchtnta     0.50     0.19

 128-KiB parallel        loads     1.00     0.39
 128-KiB parallel   prefetcht0     2.00     0.77
 128-KiB parallel   prefetcht1     1.31     0.50
 128-KiB parallel   prefetcht2     1.31     0.50
 128-KiB parallel prefetchtnta     4.10     1.58

 256-KiB parallel        loads     1.00     0.39
 256-KiB parallel   prefetcht0     2.00     0.77
 256-KiB parallel   prefetcht1     1.31     0.50
 256-KiB parallel   prefetcht2     1.31     0.50
 256-KiB parallel prefetchtnta     4.10     1.58

 512-KiB parallel        loads     4.09     1.58
 512-KiB parallel   prefetcht0     4.12     1.59
 512-KiB parallel   prefetcht1     3.80     1.46
 512-KiB parallel   prefetcht2     3.80     1.46
 512-KiB parallel prefetchtnta     4.10     1.58

2048-KiB parallel        loads     4.09     1.58
2048-KiB parallel   prefetcht0     4.12     1.59
2048-KiB parallel   prefetcht1     3.80     1.46
2048-KiB parallel   prefetcht2     3.80     1.46
2048-KiB parallel prefetchtnta    16.54     6.38

Главное отметить, что ни один из методов предварительной выборки намного быстрее, чем нагрузки при любом размере буфера. Если какая-либо команда prefetch не использовала LFB, мы ожидаем, что она будет очень быстрой для эталона, который будет соответствовать уровню кэша, который он предварительно задает. Например, prefetcht1 выводит строки в L2, поэтому для теста 128-KiB мы можем ожидать, что он будет быстрее, чем вариант загрузки, если он не использует LFB.

Более убедительно, мы можем рассмотреть счетчик l1d_pend_miss.fb_full, описание которого:

Число запросов, необходимых для запроса FB (Fill Buffer), но там для него не было никакой записи. Запрос включает требования к кешируемым/необработанным требованиям, которые загружают, сохраняют или предварительную выборку SW инструкции.

Описание уже указывает, что предварительные выборки SW нуждаются в записях LFB, и тестирование подтвердило это: для всех типов предварительной выборки этот показатель был очень высоким для любого теста, где concurrency был ограничивающим фактором. Например, для теста 512-KiB prefetcht1:

 Performance counter stats for './uarch-bench --test-name 512-KiB parallel   prefetcht1':

        38,345,242      branches                                                    
     1,074,657,384      cycles                                                      
       284,646,019      mem_inst_retired.all_loads                                   
     1,677,347,358      l1d_pend_miss.fb_full                  

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

Результаты этого теста также сокращают заявленное поведение в разделе руководства, цитируемом Leeor:

Бывают случаи, когда PREFETCH не выполняет предварительную выборку данных. К ним относятся:

  • ...
  • Если подсистема памяти заканчивается из буферов запросов между кешем первого уровня и кэшем второго уровня.

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

Также отметим следующее интересное поведение:

  • По-видимому, существует разница между prefetcht1 и prefetcht2, поскольку они сообщают о различной производительности для теста 16-KiB (разница различается, но неизменно отличается), но только этот тест!
  • Для тестов, содержащихся в L2, мы можем поддерживать 1 нагрузку за цикл, но только одну предварительную выборку prefetcht0. Это довольно странно, потому что prefetcht0 должен быть очень похож на нагрузку (и он может выдавать 2 за цикл в случаях L1).
  • Несмотря на то, что L2 имеет задержку в 12 циклов, мы можем полностью скрыть LFB с задержкой только 10 LFB: мы получаем 1,0 цикла на нагрузку (ограничено пропускной способностью L2), а не 12 / 10 == 1.2 циклов на нагрузку, d ожидать (наилучший случай), если LFB является ограничивающим фактом (и очень низкие значения для fb_full подтверждают его). Вероятно, это связано с тем, что латентность 12 циклов представляет собой полную нагрузку на использование в течение всего времени выполнения, что включает в себя также несколько циклов дополнительной задержки (например, латентность L1 составляет 4-5 циклов), поэтому фактическое время, затрачиваемое на LFB составляет менее 10 циклов.
  • Для тестов L3 мы видим значения 3.8-4.1 циклов, очень близкие к ожидаемым 42/10 = 4.2 циклам на основе L3 нагрузки на использование задержки. Таким образом, мы ограничены 10 LFB, когда мы попали в L3. Здесь prefetcht1 и prefetcht2 последовательно 0,3 цикла быстрее нагрузок или prefetcht0. Учитывая 10 LFB, что равно 3 циклам меньше занятости, более или менее объясняемым остановкой предварительной выборки на L2, а не полным доступом к L1.
  • prefetchtnta обычно имеет гораздо меньшую пропускную способность, чем другие вне L1. Вероятно, это означает, что prefetchtnta на самом деле делает то, что предполагается, и, кажется, приводит строки в L1, а не в L2 и только "слабо" в L3. Таким образом, для тестов, содержащих L2, он имеет concurrency -лимитную пропускную способность, как если бы он попадал в кеш L3, а для случая 2048-KiB (1/3 от размера кэша L3) он имел производительность при попадании в основную память. prefetchnta ограничивает загрязнение кэша L3 (что-то вроде только одного способа для каждого набора), поэтому мы, кажется, получаем выселения.

Может ли быть другим?

Вот более старый ответ, который я написал перед тестированием, размышляя над тем, как он может работать:

В общем, я ожидаю, что любая предварительная выборка приведет к тому, что данные, заканчивающиеся в L1, будут использовать буфер заполнения строки, поскольку я считаю, что единственный путь между L1 и остальной иерархией памяти - это LFB 1. Таким образом, предварительные выборки SW и HW, предназначенные для L1, вероятно, используют LFB.

Однако это оставляет открытой вероятность того, что предварительные выборки, на которых нацелены уровни L2 или выше, не потребляют LFB. Для случая предварительной выборки оборудования я вполне уверен, что это так: вы можете найти много ссылок, объясняющих, что предварительная выборка HW - это механизм, позволяющий эффективно получать больше памяти parallelism за максимум 10, предлагаемых LFB. Кроме того, похоже, что префабрики L2 не могли использовать LFB, если они хотели: они живут в L2 и близки к L2, и выдают запросы на более высокие уровни, предположительно используя суперэкспорту, и не нуждаются в LFB.

Это оставляет предварительную выборку программного обеспечения, которая нацелена на L2 (или выше), например prefetcht1 и prefetcht2 2. В отличие от запросов, сгенерированных L2, они начинаются в ядре, поэтому им нужен какой-то способ получить из ядра, и это может быть через LFB. В руководстве по оптимизации Intel есть следующая интересная цитата (выделение мое):

Как правило, предварительная выборка программного обеспечения в L2 покажет больше преимуществ чем предварительные выборки L1. Предварительная выборка программного обеспечения в L1 будет потреблять критические (заполняющий буфер), пока заполнение кешлайн не завершится. предварительная выборка программного обеспечения в L2 не содержит эти ресурсы, и это менее вероятно, окажет негативное влияние. Если вы используете L1 программных префетов, лучше всего, если предварительная выборка программного обеспечения обслуживается по хитам в кэше L2, поэтому время, в течение которого аппаратное обеспечение ресурсы сведены к минимуму.

Это, по-видимому, указывает на то, что предварительные выборки программного обеспечения не потребляют LFB - но эта цитата применима только к архитектуре Land Rover, и я не могу найти аналогичный язык для любой из более крупных архитектур. Похоже, что дизайн кеша приземления рыцарей значительно отличается (или цитата неверна).


1 На самом деле, я думаю, что даже невременные магазины используют LFB, чтобы получить выход из ядра выполнения, но их время пребывания коротка, потому что, как только они доберутся до L2, они может войти в суперэкспо (без фактического перехода в L2), а затем освободить ассоциированный LFB.

2 Я думаю, что оба из них нацелены на L2 на недавнем Intel, но это также неясно - возможно, подсказка t2 действительно нацеливает LLC на некоторые uarchs?