Руководство в формате PDF находится по адресу https://www.akkadia.org/drepper/cpumemory.pdf.
Это все еще в целом отлично и настоятельно рекомендуется (я, как я думаю, и другими экспертами по настройке производительности).Было бы здорово, если бы Ульрих (или кто-либо еще) написал обновление 2017 года, но это было бы большой работой (например, повторный запуск тестов).См. Также другие ссылки по оптимизации производительности x86 и SSE / asm (и C / C ++) в теге x86 в вики .(Статья Ульриха не является специфичной для x86, но большинство (все) его тесты относятся к аппаратному обеспечению x86.)
Сведения о низкоуровневом оборудовании о том, как работают DRAM и кэши, все еще применяются .DDR4 использует те же самые команды , которые описаны для DDR1 / DDR2 (пакетное чтение / запись).Улучшения DDR3 / 4 не являются фундаментальными изменениями.AFAIK, все не зависящие от арки вещи по-прежнему применимы в целом, например, к AArch64 / ARM32.
См. Также раздел Платформы с задержкой в этом ответе для получения важных сведений овлияние задержки памяти / L3 на однопоточную пропускную способность: bandwidth <= max_concurrency / latency
, и это фактически является основным узким местом для однопоточной пропускной способности на современном многоядерном процессоре, таком как Xeon.(Но четырехъядерный настольный ПК Skylake может приблизиться к увеличению пропускной способности DRAM с помощью одного потока).Эта ссылка содержит очень хорошую информацию о хранилищах NT и обычных хранилищах на x86.
Таким образом, предложение Ульриха в 6.5.8 Использование всей пропускной способности (с использованием удаленной памяти также на других узлах NUMA).как ваше собственное) неэффективно на современном оборудовании, где контроллеры памяти имеют большую пропускную способность, чем может использовать одно ядро.Возможно, вы можете представить себе ситуацию, когда есть несколько преимуществ использования нескольких потоков, требующих памяти, на одном узле NUMA для связи между потоками с малой задержкой, но при использовании ими удаленной памяти для высокочувствительной, не чувствительной к задержке, вещи.Но это довольно неясно;обычно вместо намеренного использования удаленной памяти, когда вы могли бы использовать локальную, просто разделите потоки между узлами NUMA и попросите их использовать локальную память.
(обычно) Не используйте программную предварительную выборку
Одна важная вещь, которая изменилась, заключается в том, что аппаратная предварительная выборка на намного лучше, чем на P4 , и может распознавать последовательные шаблоны доступа вплоть до довольно большого шага и нескольких потоков одновременно (например, один прямой/ назад за страницу 4k). Руководство по оптимизации Intel описывает некоторые детали предварительных сборщиков HW на различных уровнях кэша для их микроархитектуры семейства Sandybridge.Ivybridge и более поздние версии имеют аппаратную предварительную выборку на следующей странице, вместо того, чтобы ждать пропуска кэша на новой странице, чтобы запустить быстрый запуск.(Я предполагаю, что у AMD есть кое-что похожее в их руководстве по оптимизации.) Остерегайтесь, что руководство Intel также полно старых советов, некоторые из которых хороши только для P4.Специфичные для Sandybridge разделы, конечно, точны для SnB, но, например, в HSW изменено отсутствие ламинирования микроплавких мопов, и в руководстве это не упоминается .
Обычный совет в эти дни состоит в том, чтобы удалить всю предварительную выборку SW из старого кода , и только подумайте о том, чтобы вернуть его обратно, если профилирование показывает ошибки кэша (и вы не насыщаете пропускную способность памяти).Предварительная выборка обеих сторон шага next двоичного поиска все еще может помочь.например, как только вы решите, какой элемент смотреть следующим, предварительно выберите элементы 1/4 и 3/4, чтобы они могли загружаться параллельно с загрузкой / проверкой середины.
Предложение использовать отдельный поток предварительной выборки (6.3.4) полностью устарело , я думаю, и когда-либо было хорошо только для Pentium 4. P4 имел гиперпоточность (2 логических ядра, использующих одно физическое ядро), но недостаточно ресурсов выполнения не по порядку или кеша трассировки, чтобы увеличить пропускную способность, запустив два полных вычислительных потока на одном и том же ядре.Но современные процессоры (семейство Sandybridge и Ryzen) работают на намного более громоздкими и должны либо выполнять реальный поток, либо не использовать гиперпоточность (оставьте другое логическое ядро бездействующим, чтобы у сольного потока были все ресурсы.)
Программная предварительная выборка всегда была "хрупкой" : правильные магические числа настройки для ускорения зависят от деталей аппаратного обеспечения и, возможно, загрузки системы.Слишком рано, и оно выселено до загрузки спроса.Слишком поздно, и это не помогает. Эта статья блога показывает код + графики для интересного эксперимента по использованию предварительной выборки SW в Haswell для предварительной выборки непоследовательной части проблемы.См. Также Как правильно использовать инструкции предварительной выборки? .Предварительная выборка NT интересна, но еще более хрупка (поскольку раннее выселение из L1 означает, что вы должны пройти весь путь до L3 или DRAM, а не только до L2).Если вам нужно каждое последнее падение производительности, и , которое вы можете настроить для конкретной машины, то для предварительной последовательной загрузки стоит обратить внимание на предварительную выборку SW, но если может все равно будет замедлением, если у вас естьдостаточно работы ALU, чтобы приблизиться к узким местам в памяти.
Размер строки кэша по-прежнему составляет 64 байта.(Пропускная способность чтения / записи L1D составляет очень , и современные процессоры могут делать 2 векторных загрузки за такт + 1 векторное хранилище, если все это происходит в L1D. См. Как кэширование может быть таким быстрым? .) В AVX512 размер строки = ширина вектора, поэтому вы можете загрузить / сохранить всю строку кэша в одной инструкции.(И, таким образом, каждая неправильно выровненная загрузка / хранилище пересекает границу строки кэша, а не любую другую для 256b AVX1 / AVX2, что часто не замедляет зацикливание массива, отсутствующего в L1D.)
Нераспределенные инструкции загрузки имеют нулевое наказание, если адрес выровнен во время выполнения, но компиляторы (особенно gcc) делают лучший код при автоматическом векторизации, если они знают о каких-либо гарантиях выравнивания.Фактически невыровненные операции, как правило, бывают быстрыми, но разбиение страниц все еще вредно (хотя на Skylake гораздо меньше; задержка составляет всего ~ 11 дополнительных циклов против 100, но все равно штраф за пропускную способность).В наши дни каждая система с несколькими сокетами является NUMA: встроенные контроллеры памяти являются стандартными, то есть отсутствует внешний северный мост.Но SMP больше не означает мульти-сокет, потому что многоядерные процессоры широко распространены.(В процессорах Intel от Nehalem до Skylake использовался большой кэш включительно L3 в качестве основы для обеспечения согласованности между ядрами.) Процессоры AMD отличаются, но я не совсем уверен в деталях.
Skylake-X (AVX512) больше не имеет инклюзивного L3, но я думаю, что все еще есть каталог тегов, который позволяет ему проверять, что кешируется где-нибудь на чипе (и если да, где), без фактической передачи отслеживаний всем ядрам. SKX использует сетку, а не кольцевую шину , как правило, с еще большей задержкой, чем предыдущие многоядерные Xeon, к сожалению.
В основном все советы по оптимизации размещения памяти все еще применимы, толькодетали того, что именно происходит, когда вы не можете избежать пропусков кэша или разногласий, варьируются.
6.4.2 Атомные операции : тест, показывающий цикл повторения CAS как 4 раза хуже, чемаппаратный арбитраж lock add
, вероятно, все еще отражает случай максимальная конкуренция .Но в реальных многопоточных программах синхронизация сводится к минимуму (потому что это дорого), поэтому конкуренция низкая, и цикл CAS-retry обычно завершается успешно без повторных попыток.
C ++ 11 std::atomic
fetch_add
будет компилироваться в lock add
(или lock xadd
, если используется возвращаемое значение), но в алгоритме, использующем CAS, сделать что-то, чего нельзя сделать с помощью *Инструкция 1100 * ред обычно не беда.Используйте C ++ 11 std::atomic
или C11 stdatomic
вместо gcc legacy __sync
встроенные или более новые __atomic
встроенные модули , если вы не хотите смешивать атомарный и неатомарный доступ в одном и том же месте ...
8.1 DWCAS (cmpxchg16b
) : вы можете уговорить gcc в излучениеэто, но если вы хотите эффективную загрузку только одной половины объекта, вам нужны уродливые union
хаки: Как я могу реализовать счетчик ABA с C ++ 11 CAS? .(Не путайте DWCAS с DCAS из 2 отдельных областей памяти . Атомная эмуляция DCAS без блокировки невозможна с DWCAS, но транзакционная память (например, x86 TSX) делает этовозможно.)
8.2.4 транзакционная память : после нескольких ложных запусков (освобождается, затем отключается обновлением микрокода из-за редко вызываемой ошибки), у Intel в последнее время работает рабочая транзакционная память-модель Broadwell и все процессоры Skylake.Дизайн по-прежнему , что Дэвид Кантер описал для Haswell .Есть способ использовать блокировку для ускорения кода, который использует (и может вернуться к) обычную блокировку (особенно с одной блокировкой для всех элементов контейнера, так что несколько потоков в одной критической секции часто не сталкиваются) или написать код, который знает о транзакциях напрямую.
7.5 Огромные страницы : анонимные прозрачные огромные страницы хорошо работают в Linux без необходимости вручную использовать hugetlbfs.Сделайте выделения> = 2MiB с выравниванием 2MiB (например, posix_memalign
или aligned_alloc
, который не обеспечивает выполнение глупого требования ISO C ++ 17 для сбоя при size % alignment != 0
).
При анонимном размещении размером 2 МБ по умолчанию будут использоваться огромные страницы.Некоторые рабочие нагрузки (например, которые продолжают использовать большие выделения некоторое время после их создания) могут извлечь выгоду из
echo always >/sys/kernel/mm/transparent_hugepage/defrag
, чтобы заставить ядро дефрагментировать физическую память при необходимости, вместо того, чтобы возвращаться к страницам размером 4 КБ.(См. Документация по ядру ).В качестве альтернативы используйте madvise(MADV_HUGEPAGE)
после выполнения больших выделений (желательно с выравниванием 2 МБ).
Приложение B: Oprofile : Linux perf
в основном заменил oprofile
.Для подробных событий, характерных для определенных микроархитектур, используйте оболочку ocperf.py
.например,
ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out
Некоторые примеры его использования см. Может ли MOV x86 действительно быть "свободным"?Почему я вообще не могу воспроизвести это? .