#python #ipc #fork #shared-memory #numa
#python #ipc #форк #разделяемая память #numa
Вопрос:
Я разрабатываю научный вычислительный инструмент на Python, который должен быть способен распределять работу по нескольким ядрам в среде NUMA с общей памятью. Я ищу наиболее эффективный способ сделать это.
Потоки, к сожалению, выведены из игры из-за глобальной блокировки интерпретатора python, которая оставляет fork моим единственным вариантом. Для межпроцессного взаимодействия я полагаю, что моими вариантами являются каналы, сокеты или mmap. Пожалуйста, укажите, чего не хватает в этом списке.
Моему приложению потребуется довольно определенная связь между процессами и доступ к некоторому объему общих данных. Моя главная проблема — задержка.
Мои вопросы: когда я разветвляю процесс, будет ли его память расположена рядом с ядром, которому он назначен? Поскольку fork в копиях * nix при записи, изначально я полагаю, что этого не может быть. Хочу ли я принудительно скопировать для более быстрого доступа к памяти, и если да, то как лучше всего это сделать? Если я использую mmap для связи, может ли эта память по-прежнему распределяться по ядрам или она будет расположена в одном из них? Существует ли процесс, который прозрачно перемещает данные для оптимизации доступа? Есть ли способ иметь прямой контроль над физическим распределением или способ запросить информацию о распределении, чтобы помочь оптимизации?
На более высоком уровне, какие из этих факторов определяются моим оборудованием, а какие операционной системой? Я нахожусь в процессе покупки высокопроизводительной мультисокетной машины и сомневаюсь в выборе между AMD Opteron и Intel Xeon. Каковы последствия конкретного оборудования для любого из вышеприведенных вопросов?
Комментарии:
1. Беспокоиться о производительности на этом уровне глупо, если код написан на Python — накладные расходы интерпретатора, скорее всего, сведут на нет любые изменения, вносимые NUMA.
2. Python может вызывать подпрограммы, написанные на C, например numpy. Кроме того, Pypy делает скрипты на Python достаточно эффективными. Вопрос о том, как несколько процессов с общей памятью взаимодействуют с NUMA, также интересен.
3. Действительно, я рассматриваю Python как удобный язык для гибкого соединения фрагментов кода C вместе. При правильном выполнении я считаю, что издержки интерпретатора могут быть минимальными.
Ответ №1:
Поскольку одной из ахиллесовых пяток Python является GIL, поддержка многопроцессорности улучшена. Например, существуют очереди, каналы, блокировки, общие значения и общие массивы. Существует также нечто, называемое менеджером, которое позволяет вам обернуть множество структур данных Python и совместно использовать их в удобном для IPC виде. Я полагаю, что большинство из них работают через каналы или сокеты, но я не слишком углублялся во внутренние компоненты.
http://docs.python.org/2/library/multiprocessing.html
Как Linux моделирует системы NUMA?
Ядро обнаруживает, что оно работает на многоядерном компьютере, а затем определяет, сколько там оборудования и какова его топология. Затем он создает модель этой топологии, используя идею узлов. Узел — это физический сокет, который содержит процессор (возможно, с несколькими ядрами) и подключенную к нему память. Почему на основе узлов, а не на основе ядра? Поскольку шина памяти — это физические провода, которые соединяют ОЗУ с процессорным разъемом, и все ядра процессора в одном сокете будут иметь одинаковое время доступа ко всей оперативной памяти, которая находится на этой шине памяти.
Как доступ к памяти на одной шине памяти осуществляется ядром на другой шине памяти?
В системах x86 это происходит через кэши. Современные операционные системы используют аппаратное обеспечение, называемое буфером просмотра трансляции (TLB), для сопоставления виртуальных адресов с физическими адресами. Если память, которую кешу было поручено получить, является локальной, она считывается локально. Если он не локальный, он перейдет по шине Hyper Transport в системах AMD или QuickPath на Intel к удаленной памяти, чтобы быть удовлетворенным. Поскольку это делается на уровне кэша, вам теоретически не нужно об этом знать. И вы, конечно же, не имеете никакого контроля над этим. Но для высокопроизводительных приложений это невероятно полезно понимать, чтобы минимизировать количество удаленных обращений.
Где ОС фактически размещает физические страницы виртуальной памяти?
Когда процесс разветвляется, он наследует все родительские страницы (из-за COW). Ядро имеет представление о том, какой узел является «лучшим» для процесса, который является его «предпочтительным» узлом. Это может быть изменено, но опять же по умолчанию будет таким же, как у родительского. Распределение памяти по умолчанию будет для того же узла, что и родительский, если оно явно не изменено.
Существует ли прозрачный процесс, который перемещает память?
Нет. Как только память выделена, она привязывается к узлу, на котором она была выделена. Вы можете создать новое распределение на другом узле, переместить данные и освободить их на первом узле, но это немного утомительно.
Есть ли способ контролировать распределение?
По умолчанию используется выделение для локального узла. Если вы используете libnuma, вы можете изменить способ выделения (скажем, циклически или с чередованием) вместо того, чтобы по умолчанию использовать local.
Я почерпнул много информации из этого поста в блоге:
http://blog.jcole.us/2010/09/28/mysql-swap-insanity-and-the-numa-architecture/
Я бы определенно рекомендовал вам прочитать его полностью, чтобы почерпнуть дополнительную информацию.