Что должен знать каждый программист о памяти PDF

Обновлено: 21.11.2024

Я за то, чтобы все знать, просто чтобы знать. но зачем программисту знать, ПОЧЕМУ потоковая передача данных эффективна?

Потому что, по сути, DRAM является основным компонентом компьютера, точно так же, как ядра ЦП являются основным компонентом компьютера.

Теперь я лично объяснил бы вещи гораздо проще, чем то, что было описано в PDF. Вот факты, которые необходимо знать программистам:

<р>1. DRAM хранит данные в очень крошечных конденсаторах. Эти крошечные конденсаторы обладают двумя свойствами: они разряжаются всего за 64 мс. А во-вторых, у них заканчивается электричество после ОДНОЙ операции чтения.

<р>2. DRAM имеет временное место, называемое «усилителями считывания», где данные сохраняются во время обновления или чтения. Эти усилители чувств могут хранить данные постоянно.

<р>3. Это «временное чтение» называется Row-open (или RAS). Чтение из уже открытой строки называется Column-read (CAS). Отправка данных обратно в DRAM называется Precharge (PRE). Помните, усилители считывания должны быть очищены, прежде чем они смогут читать новую строку. (Старые данные в DRAM были уничтожены, когда вы читали их с помощью операции RAS)

<р>4. Я предполагаю, что есть периодическое обновление, о котором вы должны знать: вместо того, чтобы пытаться исправить всю оперативную память каждые 64 мс, вы должны делать это небольшими порциями за раз. Каждую дюжину микросекунд ОЗУ будет самостоятельно читать/самозаписывать, чтобы обновить следующую строку. Не удивляйтесь, если из-за этого обновления ваши операции чтения из памяти случайно задержатся на несколько сотен наносекунд.

Конец. Не так уж и сложно, не так ли?

DRAM работает быстрее при потоковой передаче, потому что вы открываете строку один раз, заполняете все данные в строке, а затем отправляете строку обратно в DRAM. По сути, вам нужно всего лишь выполнить несколько операций записи «столбцов» в усилители считывания, а не открывать и закрывать несколько разных строк.

Так что да, программисты должны это знать, потому что научиться этому на самом деле не так сложно :-) И если вы начнете измерять свою программу на уровне наносекунд, вы действительно увидите эти эффекты и начнете требовать объяснений.

EDIT: Хммм. чем больше я об этом думаю, тем меньше это нужно знать «программистам» и что «должны знать системные администраторы / DevOps». Опытный системный администратор может использовать эти инструменты профилировщика, чтобы выяснить, нужен ли ему компьютер с 6-кратным каналом памяти или компьютер с 8-кратным каналом памяти при следующей покупке.

Привязан ли ваш код к памяти? Или это связано с процессором? Стоит ли покупать больше ядер? Стоит ли покупать больше LRDIMM для большего объема оперативной памяти? Или ваша программа ограничена задержкой и на самом деле выигрывает от более низкой задержки RDIMM или даже UDIMM?

Программисты как бы не принимают такие решения.

Я бы сказал, что это объяснение заслуживает названия "Что должен знать каждый программист", в большей степени, чем исходные двенадцать или около того страниц.

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

кто-нибудь знает, могу ли я где-нибудь скачать эту статью в формате mobi, чтобы я мог легко прочитать ее на Kindle? "pdf" очень трудно читать из-за проблем с масштабированием/форматированием

3 ответа 3

В целом он по-прежнему превосходен и настоятельно рекомендуется (мной и, думаю, другими экспертами по настройке производительности). Было бы здорово, если бы Ульрих (или кто-то другой) написал обновление 2017 года, но это потребовало бы много работы (например, повторный запуск тестов). См. также другие ссылки по настройке производительности x86 и оптимизации SSE/asm (и C/C++) в вики-странице тегов x86. (Статья Ульриха не посвящена x86, но большинство (все) его тесты относятся к оборудованию x86.)

Низкоуровневая аппаратная информация о том, как работают DRAM и кэши, по-прежнему применима. DDR4 использует те же команды, что и для DDR1/DDR2 (пакетное чтение/запись). Улучшения DDR3/4 не являются фундаментальными изменениями. AFAIK, все архинезависимые вещи по-прежнему применяются в целом, например. в AArch64/ARM32.

Важные сведения о влиянии задержки памяти/L3 на однопотоковую пропускную способность см. также в разделе «Платформы с привязкой к задержке» этого ответа: пропускная способность, и это на самом деле основное узкое место для однопоточной пропускной способности на современном многоядерном процессоре. Процессор как Xeon. Но четырехъядерный настольный компьютер Skylake может приблизиться к максимальной пропускной способности DRAM с помощью одного потока. Эта ссылка содержит очень полезную информацию о магазинах NT и обычных магазинах на x86. Почему Skylake намного лучше, чем Broadwell-E, по пропускной способности однопоточной памяти? это резюме.

Таким образом, предложение Ульриха в разделе 6.5.8 Использование всей пропускной способности об использовании удаленной памяти на других узлах NUMA, а также на вашей собственной, является контрпродуктивным на современном оборудовании, где контроллеры памяти имеют большую пропускную способность, чем может использовать одно ядро.Ну, возможно, вы можете представить себе ситуацию, когда есть чистая выгода от запуска нескольких потоков, жадных до памяти, на одном узле NUMA для связи между потоками с малой задержкой, но при этом они используют удаленную память для высокой пропускной способности, не чувствительной к задержке. Но это довольно неясно, обычно просто делят потоки между узлами NUMA и заставляют их использовать локальную память. Пропускная способность на ядро ​​чувствительна к задержке из-за ограничений максимального количества параллелизма (см. ниже), но все ядра в одном сокете обычно могут более чем насыщать контроллеры памяти в этом сокете.

(обычно) Не ​​использовать программную предварительную выборку

Одним из основных изменений является то, что аппаратная предварительная выборка намного лучше, чем на Pentium 4, и может распознавать пошаговые шаблоны доступа с довольно большим шагом и несколько потоков одновременно (например, один прямой / назад на страницу 4k). В руководстве Intel по оптимизации описаны некоторые детали аппаратных предварительных выборок в различных уровнях кэш-памяти для их микроархитектур семейства Sandybridge. Ivybridge и более поздние версии имеют аппаратную предварительную выборку следующей страницы вместо ожидания промаха кеша на новой странице, чтобы вызвать быстрый запуск. Я предполагаю, что у AMD есть что-то подобное в их руководстве по оптимизации. Имейте в виду, что руководство Intel также полно старых советов, некоторые из которых подходят только для P4. Разделы, относящиеся к Sandybridge, конечно, точны для SnB, но, например. Неламинирование микроплавких МОП изменено в HSW, и в руководстве об этом не упоминается.

Обычно в наши дни рекомендуется удалить всю предварительную выборку SW из старого кода и рассмотреть возможность ее повторного использования только в том случае, если профилирование показывает промахи кеша (и вы не перегружаете пропускную способность памяти). Предварительная выборка обеих сторон следующего шага бинарного поиска все еще может помочь. например как только вы решите, на какой элемент смотреть дальше, выполните предварительную выборку элементов 1/4 и 3/4, чтобы они могли загружаться параллельно с загрузкой/проверкой середины.

Предложение использовать отдельный поток предварительной выборки (6.3.4), как мне кажется, полностью устарело и годилось только для Pentium 4. В P4 была гиперпоточность (два логических ядра, совместно использующих одно физическое ядро), но недостаточно трассировки. кэш (и/или неупорядоченные ресурсы выполнения) для увеличения пропускной способности при выполнении двух полных вычислительных потоков на одном ядре. Но современные процессоры (семейство 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 Atomic ops: эталонный тест, показывающий, что петля CAS-retry в 4 раза хуже, чем добавление блокировки с аппаратным арбитражем, вероятно, по-прежнему отражает случай максимальной конкуренции. Но в реальных многопоточных программах синхронизация сведена к минимуму (потому что это дорого), поэтому конфликты невелики, а цикл повторных попыток CAS обычно завершается успешно без повторных попыток.

C++11 std::atomic fetch_add будет компилироваться в блокировку add (или блокировку xadd, если используется возвращаемое значение), но алгоритм, использующий CAS для выполнения чего-то, чего нельзя сделать с помощью инструкции lock ed, обычно не катастрофа. Используйте C++11 std::atomic или C11 stdatomic вместо устаревших встроенных функций gcc __sync или более новых встроенных функций __atomic, если вы не хотите смешивать атомарный и неатомарный доступ к одному и тому же местоположению.

8.1 DWCAS ( cmpxchg16b ): вы можете уговорить gcc выдать его, но если вам нужна эффективная загрузка только одной половины объекта, вам нужны уродливые хаки с объединением: как я могу реализовать счетчик ABA с CAS C++ 11? . (Не путайте DWCAS с DCAS с двумя отдельными ячейками памяти. Атомарная эмуляция DCAS без блокировки невозможна с DWCAS, но транзакционная память (например, x86 TSX) делает это возможным.)

8.2.4 транзакционная память: после пары неудачных запусков (выпущенных, а затем отключенных обновлением микрокода из-за редко возникающей ошибки) у Intel есть рабочая транзакционная память в последней модели Broadwell и во всех процессорах Skylake. Дизайн все тот же, что описал Дэвид Кантер для Haswell. Существует способ исключения блокировки для ускорения кода, который использует обычную блокировку (и может вернуться к ней) (особенно с одной блокировкой для всех элементов контейнера, поэтому несколько потоков в одном и том же критическом разделе часто не сталкиваются). ) или написать код, который знает о транзакциях напрямую.

Обновление: теперь Intel отключила блокировку на более поздних процессорах (включая Skylake) с обновлением микрокода. Непрозрачная часть TSX с RTM (xbegin/xend) все еще может работать, если это позволяет ОС, но в целом TSX серьезно превращается в футбольный мяч Чарли Брауна.

    (Да, но из-за уязвимости стороннего канала типа MDS (TAA), а не Spectre. Насколько я понимаю, обновленный микрокод полностью отключает HLE. В этом случае ОС может включить только RTM, а не HLE.)

7.5 Hugepages: анонимные прозрачные огромные страницы хорошо работают в Linux без необходимости вручную использовать hugetlbfs. Сделайте выделения >= 2 МиБ с выравниванием по 2 МиБ (например, posix_memalign или выровненный_аллок, который не применяет дурацкое требование ISO C++ 17 для отказа, когда размер % выравнивания != 0).

Анонимное распределение с выравниванием по 2 МиБ будет использовать огромные страницы по умолчанию. Некоторым рабочим нагрузкам (например, которые продолжают использовать большие объемы памяти в течение некоторого времени после их создания) может помочь
echo defer+madvise >/sys/kernel/mm/transparent_hugepage/defrag, чтобы вместо этого заставить ядро ​​дефрагментировать физическую память, когда это необходимо. откатиться к 4k страницам. (См. документацию по ядру). Используйте madvise(MADV_HUGEPAGE) после больших выделений памяти (желательно с выравниванием по 2МиБ), чтобы сильнее стимулировать остановку ядра и дефрагментацию сейчас. Дефрагментация = всегда слишком агрессивна для большинства рабочих нагрузок и тратит больше времени на копирование страниц, чем экономит промахи TLB. (kcompactd может быть более эффективным.)

Кстати, Intel и AMD называют страницы размером 2 МБ "большими страницами", а слово "огромный" используется только для страниц размером 1 ГБ. Linux использует "hugepage" для всего, что больше стандартного размера.

(Таблицы страниц 32-разрядного режима (не-PAE) имели только 4 МБ в качестве следующего по величине размера, с только двухуровневыми таблицами страниц с более компактными записями. Следующим размером был бы 4 ГБ, но это все адресное пространство, и этот «уровень» трансляции — это управляющий регистр CR3, а не запись каталога страниц. IDK, если это связано с терминологией Linux.)

Приложение B: Oprofile: Linux perf в основном заменил oprofile . список производительности / статистика производительности -e событие1,событие2 . имеет имена для большинства полезных способов программирования счетчиков производительности HW.

Читайте также: