Что каждый программист должен знать о памяти? - PullRequest
129 голосов
/ 14 ноября 2011

Мне интересно, сколько из Ульриха Дреппера * Что каждый программист должен знать о памяти с 2007 года по-прежнему действует.Также я не смог найти более новую версию, чем 1.0 или опечатки.

Ответы [ 3 ]

91 голосов
/ 14 ноября 2011

Насколько я помню, содержание Дреппера описывает фундаментальные понятия о памяти: как работает кэш процессора, что такое физическая и виртуальная память и как ядро ​​Linux работает с этим зоопарком. Возможно, в некоторых примерах есть устаревшие ссылки на API, но это не имеет значения; это не повлияет на актуальность фундаментальных понятий.

Таким образом, любую книгу или статью, описывающую нечто фундаментальное, нельзя назвать устаревшей. «То, что каждый программист должен знать о памяти», безусловно, стоит прочитать, но, ну, я не думаю, что это «для каждого программиста». Это больше подходит для парней системы / встроенного / ядра.

83 голосов
/ 08 декабря 2017

Руководство в формате PDF находится по адресу https://www.akkadia.org/drepper/cpumemory.pdf.

Это все еще в целом отлично и настоятельно рекомендуется (я, как я думаю, и другими экспертами по настройке производительности).Было бы здорово, если бы Ульрих (или кто-либо еще) написал обновление 2017 года, но это было бы большой работой (например, повторный запуск тестов).См. Также другие ссылки по оптимизации производительности x86 и SSE / asm (и C / C ++) в теге в вики .(Статья Ульриха не является специфичной для 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 действительно быть "свободным"?Почему я вообще не могу воспроизвести это? .

70 голосов
/ 14 ноября 2011

На мой быстрый взгляд это выглядит довольно точно.Единственное, на что следует обратить внимание - это разница между «встроенными» и «внешними» контроллерами памяти.С момента выпуска линейки Intel i7 все процессоры интегрированы, и AMD использует встроенные контроллеры памяти с момента выпуска первых чипов AMD64.

С тех пор, как была написана эта статья, не так много изменилось, скорости стали выше, контроллеры памяти стали намного интеллектуальнее (i7 будет откладывать запись в ОЗУ, пока не почувствуете, что зафиксировал изменения), ноне многое изменилось.По крайней мере, в любом случае, что разработчик программного обеспечения будет заботиться.

...