Когда вычисления с ограниченной пропускной способностью памяти выполняются в средах с общей памятью (например, с потоками через OpenMP, Pthreads или TBB), возникает дилемма, как обеспечить правильное распределение памяти по физической памяти, так что каждый поток в основном обращается к памяти в «местная» шина памяти. Хотя интерфейсы не являются переносимыми, большинство операционных систем имеют способы установки схожести потоков (например, pthread_setaffinity_np()
во многих системах POSIX, sched_setaffinity()
в Linux, SetThreadAffinityMask()
в Windows). Существуют также библиотеки, такие как hwloc, для определения иерархии памяти, но, к сожалению, большинство операционных систем пока не предоставляют способы установки политик памяти NUMA. Linux является заметным исключением, с libnumaпозволяя приложению манипулировать политикой памяти и миграцией страниц с гранулярностью страниц (в основном с 2004 года, поэтому широко доступна). Другие операционные системы ожидают, что пользователи будут соблюдать неявную политику «первого прикосновения».
Работа с политикой «первого прикосновения» означает, что вызывающая сторона должна создавать и распространять потоки с той привязкой, которую они планируют использовать позже при первой записи в только что выделенную память. (Очень немногие системы настроены таким образом, что malloc()
фактически находят страницы, просто обещают найти их, когда они действительно повреждены, возможно, разными потоками.) Это подразумевает, что распределение с использованием calloc()
или немедленная инициализация памяти после выделения memset()
вредно, так как это приведет к сбою вся память на шину памяти ядра, на котором выполняется распределительный поток, что приводит к пропускной способности памяти в худшем случае при обращении к памяти из нескольких потоков. То же самое относится к new
оператору C ++, который настаивает на инициализации многих новых распределений (например,std::complex
). Некоторые наблюдения об этой среде:
- Распределение можно сделать «коллективным потоком», но теперь распределение становится смешанным с моделью потоков, что нежелательно для библиотек, которым, возможно, придется взаимодействовать с клиентами, использующими разные модели потоков (возможно, каждая со своими собственными пулами потоков).
- Считается, что RAII является важной частью идиоматического C ++, но, похоже, он активно наносит ущерб производительности памяти в среде NUMA. Размещение
new
может использоваться с памятью, выделенной черезmalloc()
или из подпрограммlibnuma
, но это меняет процесс выделения (который, я считаю, необходим). - РЕДАКТИРОВАТЬ: мое предыдущее утверждение об операторе
new
было неверным, он может поддерживать несколько аргументов, см. Ответ Четана. Я полагаю, что все еще существует проблема получения библиотек или контейнеров STL для использования указанного соответствия. Несколько полей могут быть упакованы, и может быть неудобно гарантировать, например,std::vector
перераспределение с правильным активным менеджером контекста. - Каждый поток может выделить и сбросить свою собственную личную память, но тогда индексация в соседние регионы будет более сложной. (Рассмотрим разреженное матрично-векторное произведение с разделением строки матрицы и векторов; для индексации неизвестной части x требуется более сложная структура данных, когда x не является смежным в виртуальной памяти.)
Какие-либо решения для распределения / инициализации NUMA считаются идиоматическими? Я пропустил другие критические ошибки?
(Я не имею в виду для моего C примеры ++ подразумевает акцент на том языке, однако C ++ язык кодирует некоторые решения об управлении памятью , что язык , как C не происходит , таким образом , существует тенденция к более сопротивления , когда предполагая , что C ++ программисты делают те , все по-другому.)