Что Каждый Программист Должен Знать О Памяти?
Мне интересно, сколько из Ульриха Дреппера Что Каждый Программист Должен Знать О Памяти!--2--> С 2007 года остается в силе. Также я не мог найти более новую версию, чем 1.0 или описки.
3 ответов:
насколько я помню, содержание Drepper описывает фундаментальные понятия о памяти: как работает кэш процессора, что такое физическая и виртуальная память и как ядро Linux имеет дело с этим зоопарком. Возможно, в некоторых примерах есть устаревшие ссылки на API, но это не имеет значения; это не повлияет на актуальность фундаментальных понятий.
Итак, любая книга или статья, описывающая что-то фундаментальное, не может быть названа устаревшей. "Что каждый программист должен знать о памяти" определенно стоит прочитать, но, Ну, я не думаю, что это для "каждого программиста". Это больше подходит для системных / встроенных / ядра парней.
от моего быстрого взгляда-через это выглядит довольно точно. Единственное, что нужно заметить, это часть о разнице между" интегрированными "и" внешними " контроллерами памяти. С момента выпуска линейки i7 процессоры Intel все интегрированы, и AMD использует интегрированные контроллеры памяти с момента первого выпуска чипов AMD64.
Так как эта статья была написана, не все изменилось, скорость стала выше, контроллеры памяти получили гораздо больше интеллектуальный (i7 будет задерживать запись в ОЗУ, пока не почувствует, что совершает изменения), но не так много изменилось. По крайней мере, не в любом случае, что разработчик программного обеспечения будет волновать.
руководство в формате PDF находится по адресу https://www.akkadia.org/drepper/cpumemory.pdf.
это все же очень обидно. (мной, и я думаю, другими экспертами по настройке производительности). Было бы здорово, если бы Ульрих (или кто-то еще) написал обновление 2017 года, но это было бы много работы (например, повторный запуск тестов). См. также другие ссылки на оптимизацию производительности x86 и SSE/asm (и C / C++) в x86 метки. (Статья Ульриха не относится к x86, но большинство (все) его тестов находятся на оборудовании x86.)
аппаратные детали низкого уровня о том, как работают DRAM и кэши, все еще применяются. Память DDR4 использует те же команды как описано для DDR1 / DDR2 (чтение / запись пакета). Улучшения DDR3 / 4 не являются фундаментальными изменениями. AFAIK, все arch-независимые вещи по-прежнему применяются в целом, например, к AArch64 / ARM32.
посмотреть также the задержка привязки платформы раздел этого ответа для получения важных сведений о влиянии задержки memory/L3 на однопоточную пропускную способность:
bandwidth <= max_concurrency / latency, и это фактически основное узкое место для однопоточной пропускной способности на современном многоядерном процессоре, таком как Xeon. (Но четырехъядерный рабочий стол Skylake может приблизиться к максимальному увеличению пропускной способности DRAM с помощью одного потока). Эта ссылка имеет очень хорошую информацию о магазинах NT и обычных магазинах архитектуры x86.таким образом предложение Ульриха в 6.5.8 Использование Всей Полосы Пропускания (при использовании удаленной памяти на других узлах NUMA, а также на вашем собственном) является контрпродуктивным на современном оборудовании, где контроллеры памяти имеют большую пропускную способность, чем может использовать одно ядро. Ну, возможно, вы можете представить себе ситуацию, когда есть некоторое преимущество для запуска нескольких потоков памяти на одном узле NUMA для межпотоковой связи с низкой задержкой, но при этом они используют удаленную память для высокая пропускная способность, не чувствительная к задержке. Но это довольно неясно; обычно вместо намеренного использования удаленной памяти, когда вы могли бы использовать локальную, просто разделите потоки между узлами NUMA и попросите их использовать локальную память.
(обычно) не используйте программное обеспечение prefetch
одна важная вещь, которая изменилась, это то, что аппаратная предварительная выборка много лучше, чем на P4 и смогите узнать strided картины доступа до справедливо большого шаг и несколько потоков одновременно (например, один вперед / назад на страницу 4k). руководство по оптимизации Intel описывает некоторые детали HW prefetchers в различных уровнях кэша для их микроархитектуры семейства Sandybridge. Ivybridge и позже имеют аппаратную предварительную выборку следующей страницы, вместо того, чтобы ждать промаха кэша на новой странице, чтобы вызвать быстрый запуск. (Я предполагаю, что AMD имеет некоторые подобные вещи в своем Руководстве по оптимизации.) Помните, что руководство Intel также полно старых советы, некоторые из которых хороши только для P4. Конкретные разделы Sandybridge, конечно, точны для SnB, но, например,un-ламинирование микро-сплавленных uops изменилось в HSW, и руководство не упоминает об этом.
обычный совет в эти дни, чтобы удалить все SW prefetch из старого кода, и только подумайте о том, чтобы вернуть его, если профилирование показывает промахи кэша (и вы не насыщаете пропускную способность памяти). Предварительная выборка с обеих сторон далее шаг двоичного поиска все еще может помочь. например, как только вы решите, какой элемент смотреть дальше, предварительно выберите элементы 1/4 и 3/4, чтобы они могли загружаться параллельно с загрузкой/проверкой середины.
предложение использовать отдельный поток предварительной выборки (6.3.4) полностью устарело, я думаю, и был только хорошо на Pentium 4. P4 имел гиперпоточность (2 логических ядра, разделяющих одно физическое ядро), но недостаточно ресурсов для выполнения вне порядка или трассировка-кэш для получения пропускной способности, выполняющей два полных вычислительных потока на одном ядре. Но современные процессоры (Sandybridge-family и Ryzen) - это много beefier и должен либо запускать реальный поток, либо не использовать hyperthreading (оставьте другое логическое ядро бездействующим, чтобы у одиночного потока были полные ресурсы.)
предварительная выборка программного обеспечения всегда была "хрупкой": правильные магические номера настройки для ускорения зависят от деталей оборудования и, возможно, системы нагрузка. Слишком рано, и он выселяется до нагрузки спроса. Слишком поздно и это не поможет. в этой статье показывает код + графики для интересного эксперимента по использованию SW prefetch на Haswell для предварительной выборки непоследовательной части проблемы. Смотрите также Как правильно использовать инструкции prefetch?. NT prefetch интересен, но еще более хрупок (потому что раннее выселение из L1 означает, что вам нужно пройти весь путь до L3 или DRAM, а не только L2). Если вам нужно до последней капли производительности,и вы можете настроить для конкретной машины, SW prefetch стоит посмотреть на последовательный доступ, но если мая все еще будет замедление, если у вас достаточно работы ALU, чтобы сделать, приближаясь к узкому месту в памяти.
размер строки кэша по-прежнему составляет 64 байта. (Пропускная способность чтения/записи L1D составляет очень высокие, и современные процессоры могут делать 2 вектора нагрузок в часы + 1 вектора магазине, если все это попадает в L1D. См как кэш может быть так быстро?.) С AVX512, размер линии = ширина вектора, так что вы можете загрузить/сохранить всю строку кэша в одной инструкции. (И, таким образом, каждая несоосная загрузка / хранилище пересекает границу кэш-линии, а не каждую другую для 256b AVX1 / AVX2, которая часто не замедляет цикл по массиву, который не был в L1D.)
невыровненные инструкции загрузки имеют нулевой штраф, если адрес выровнен во время выполнения, но компиляторы (особенно gcc) улучшают код, когда автовекторизация, если они знают о каких-либо гарантиях выравнивания. На самом деле невыровненные ops, как правило, быстро, но разбиение страниц все еще больно (гораздо меньше на Skylake, хотя; только ~11 дополнительных циклов задержки против 100, но все же штраф за пропускную способность).
как и предсказывал Ульрих, каждый Multi-гнездо система NUMA в наши дни: интегрированные контроллеры памяти являются стандартными, т. е. нет внешнего Северного моста. Но SMP больше не означает мульти-сокет, потому что многоядерные процессоры широко распространенный. (Процессоры Intel от Nehalem до Skylake использовали большой включительно кэш L3 в качестве опоры для обеспечения согласованности между ядрами.) Процессоры AMD разные, но я не так ясно понимаю детали.
Skylake-X (AVX512) больше не имеет инклюзивного L3, но я думаю, что все еще есть каталог тегов, который позволяет ему проверять, что кэшируется в любом месте на чипе (и если да, то где) без фактического вещания snoops на все ядра. SKX использует сетку, а не кольцо автобус, как правило, с еще худшей задержкой, чем предыдущие многоядерные Xeons, к сожалению.
в основном все советы по оптимизации размещения памяти по-прежнему применяются, просто детали того, что именно происходит, когда вы не можете избежать промахов кэша или разногласий, различаются.
6.4.2 Atomic ops: бенчмарк показывает в CAS-повторить петли в 4 раза хуже, чем аппаратно-решается
lock addвероятно, все еще отражает a максимум утверждение случае. Но в реальных многопоточных программах синхронизация сведена к минимуму (потому что это дорого), поэтому конкуренция низка, и цикл CAS-retry обычно успешно выполняется без необходимости повторять попытку.C++11
std::atomicfetch_addбудет составлять сlock add(илиlock xaddесли используется возвращаемое значение), но алгоритм, использующий CAS, чтобы сделать что-то, что не может быть сделано с помощьюlockинструкция ed обычно не является катастрофой. Используйте C++11std::atomicили C11stdatomicвместо GCC legacy__syncвстроенные модули или новее__atomicвстроенные модули если вы не хотите смешивать атомарный и неатомный доступ к одному и тому же местоположению...8.1 DCAS (
cmpxchg16b): вы можете уговорить gcc излучать его, но если вы хотите эффективные нагрузки только на одну половину объекта, вам нужно уродливоunionхаки: как я могу реализовать счетчик ABA с c++11 КАС?8.2.4 транзакций: после нескольких ложных запусков (выпущенных затем отключенных обновлением микрокода из-за редко срабатывающей ошибки) Intel имеет рабочую транзакционную память в последней модели Broadwell и всех процессорах Skylake. Дизайн по-прежнему то, что Дэвид Кантер описал для Хасвелла. Есть способ lock-ellision использовать его для ускорения кода, который использует (и может вернуться) обычный замок (особенно с одним замком для всех элементы контейнера, поэтому несколько потоков в одном и том же критическом разделе часто не сталкиваются), или написать код, который знает о транзакциях напрямую.
7.5 Hugepages: анонимные прозрачные hugepages хорошо работают на Linux без необходимости вручную использовать hugetlbfs. Сделайте распределения >= 2MiB с выравниванием 2MiB (например
posix_memalignилиaligned_allocэто не обеспечивает выполнение глупого требования ISO C++17 для сбоя, когдаsize % alignment != 0).2mib-выровненное анонимное распределение будет использовать огромные страницы по умолчанию. Некоторые рабочие нагрузки (например, которые продолжают использовать большие выделения в течение некоторого времени после их создания) могут извлечь выгоду из
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некоторые примеры его использования см. В разделе может ли движение x86 действительно быть "свободным"? Почему я не могу воспроизвести это вообще?.
Comments