Многопроцессорная обработка: использовать только физические ядра?

у меня есть функция foo, который потребляет много памяти и что я хотел бы запустить несколько экземпляров параллельно.

Предположим, у меня есть процессор с 4 физическими ядрами, каждый с двумя логическими ядрами.

в моей системе достаточно памяти для размещения 4 экземпляров foo параллельно, но не 8. Кроме того, поскольку 4 из этих 8 ядер являются логическими, я также не ожидаю, что использование всех 8 ядер обеспечит много преимуществ выше и за пределами использования 4 физических только.

поэтому я хочу бежать foo на 4 физических ядер только. Другими словами, Я хотел бы убедиться, что doing multiprocessing.Pool(4) (4 - максимальное количество одновременного запуска функции, которое я могу разместить на этом компьютере из-за ограничений памяти) отправляет задание четырем физическим ядрам (а не, например, комбо из двух физических ядер и их двух логических потомков).

как это сделать в в Python?

Edit:

ранее я использовал пример кода из multiprocessing но я агностик библиотеки, поэтому, чтобы избежать путаницы, я удалил это.

3 ответов


Примечание: этот подход не работает в windows и тестируется только в linux.

используя multiprocessing.Process:

назначение физического ядра для каждого процесса довольно легко при использовании Process(). Вы можете создать цикл for, который выполняет итерацию по каждому ядру и назначает новый процесс новому ядру с помощью taskset -p [mask] [pid] :

import multiprocessing
import os

def foo():
    return

if __name__ == "__main__" :
    for process_idx in range(multiprocessing.cpu_count()):
        p = multiprocessing.Process(target=foo)
        os.system("taskset -p -c %d %d" % (process_idx % multiprocessing.cpu_count(), os.getpid()))
        p.start()

у меня есть 32 ядра на моей рабочей станции, поэтому я поставлю частичные результаты здесь:

pid 520811's current affinity list: 0-31
pid 520811's new affinity list: 0
pid 520811's current affinity list: 0
pid 520811's new affinity list: 1
pid 520811's current affinity list: 1
pid 520811's new affinity list: 2
pid 520811's current affinity list: 2
pid 520811's new affinity list: 3
pid 520811's current affinity list: 3
pid 520811's new affinity list: 4
pid 520811's current affinity list: 4
pid 520811's new affinity list: 5
...

как вы видите, предыдущее и новое сродство каждого процесса здесь. Первый-для всех ядер (0-31), а затем присваивается ядру 0, второй процесс по умолчанию присваивается core0, а затем его сродство изменяется на следующее ядро (1) и т. д.

используя multiprocessing.Pool:

предупреждение: этот подход требует настройки pool.py модуль, так как я не знаю, что вы можете извлечь pid от Pool(). Также эти изменения были протестированы на python 2.7 и multiprocessing.__version__ = '0.70a1'.

на Pool.py найдите строку, где _task_handler_start() метод вызывается. В следующей строке вы можете назначить процесс в пуле каждому "физическому" ядру с помощью (я поставил import os здесь, чтобы читатель не забыл импортировать его):

import os
for worker in range(len(self._pool)):
    p = self._pool[worker]
    os.system("taskset -p -c %d %d" % (worker % cpu_count(), p.pid))

и вы сделали. Тест:

import multiprocessing

def foo(i):
    return

if __name__ == "__main__" :
    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    pool.map(foo,'iterable here')

результат:

pid 524730's current affinity list: 0-31
pid 524730's new affinity list: 0
pid 524731's current affinity list: 0-31
pid 524731's new affinity list: 1
pid 524732's current affinity list: 0-31
pid 524732's new affinity list: 2
pid 524733's current affinity list: 0-31
pid 524733's new affinity list: 3
pid 524734's current affinity list: 0-31
pid 524734's new affinity list: 4
pid 524735's current affinity list: 0-31
pid 524735's new affinity list: 5
...

обратите внимание, что эта модификация pool.py назначьте задания ядрам round-robinly. Поэтому, если вы назначаете больше заданий, чем cpu-ядер, у вас будет несколько из них на одном ядре.

EDIT:

то, что OP ищет, это иметь pool() это способно смотреть на пул на определенных ядрах. Для этого больше настроек на multiprocessing необходимы (сначала отмените вышеупомянутые изменения).

предупреждение:

не пытайся copy-вставить определения функций и вызовы функций. Только скопируйте вставить часть, которая должна быть добавлена после self._worker_handler.start() (вы увидите ее ниже). Обратите внимание, что мой multiprocessing.__version__ говорит мне, что версия '0.70a1', но это не имеет значения, пока вы просто добавляете то, что вам нужно добавить:

multiprocessing ' s pool.py:

добавить до __init__() определение. В моей версии это выглядит так после добавления его:

def __init__(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None,cores_idx=None)

также вы должны добавьте следующий код после self._worker_handler.start():

if not cores_idx is None:
    import os
    for worker in range(len(self._pool)):
        p = self._pool[worker]
        os.system("taskset -p -c %d %d" % (cores_idx[worker % (len(cores_idx))], p.pid))

multiprocessing ' s __init__.py:

добавить cores_idx=None аргумент к определению Pool() в, а также другие Pool() вызов функции в обратный. В моей версии это выглядит так:

def Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None,cores_idx=None):
    '''
    Returns a process pool object
    '''
    from multiprocessing.pool import Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,cores_idx)

и вы сделали. В следующем примере выполняется пул из 5 работников только на ядрах 0 и 2:

import multiprocessing


def foo(i):
    return

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=5,cores_idx=[0,2])
    pool.map(foo,'iterable here')

результат:

pid 705235's current affinity list: 0-31
pid 705235's new affinity list: 0
pid 705236's current affinity list: 0-31
pid 705236's new affinity list: 2
pid 705237's current affinity list: 0-31
pid 705237's new affinity list: 0
pid 705238's current affinity list: 0-31
pid 705238's new affinity list: 2
pid 705239's current affinity list: 0-31
pid 705239's new affinity list: 0

конечно можно еще имейте обычную функциональность multiprocessing.Poll() а также путем удаления


Я знаю, что тема довольно старая, но, как она по-прежнему появляется в качестве первого ответа при вводе "многопроцессорного логического ядра" в google... Я чувствую, что должен дать дополнительный ответ, потому что я вижу, что это будет возможно для людей в 2018 году (или даже позже..) чтобы легко запутаться здесь (некоторые ответы действительно немного запутаны)

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

--> для подсчета процессоров (логических / физических) используйте модуль PSUTIL

для потока 4 physical core / 8 i7 for ex он вернется

import psutil 
psutil.cpu_count(logical = False)

4

psutil.cpu_count(logical = True)

8

так просто.

там вам не придется беспокоиться об ОС, платформе, самом оборудовании или что-то еще. Я убежден, что это гораздо лучше, чем многопроцессорный.cpu_count() который иногда может давать странные результаты, по крайней мере, из моего собственного опыта.

--> для использования физического ядра (по вашему выбору) используйте модуль многопроцессорной обработки, описанный YUGI

просто посчитайте, сколько физические процессы, запуск многопроцессорной.Бассейн из 4-х человек.

или вы также можете попробовать использовать joblib.Параллельно (функция)

joblib в 2018 году не является частью стандартного дистрибутива python, но это просто оболочка многопроцессорного модуля, который был описан Yugi.

--> большую часть времени не используйте больше ядер, чем доступно (если вы не проверили очень конкретный код и не доказали, что он того стоит)

мы можем слышать здесь и там (также от некоторых людей, отвечающих здесь), что "ОС позаботится о том, чтобы вы использовали больше ядра, чем доступно". это абсолютно 100% ложь. Если вы используете больше core, чем доступно, вы столкнетесь с огромными падениями производительности. Потому что планировщик ОС будет стараться изо всех сил работать над каждой задачей с одинаковым вниманием, регулярно переключаясь с одной на другую, и в зависимости от ос он может потратить до 100% своего рабочего времени на простое переключение между процессами, что было бы катастрофично.

не просто доверяйте мне: попробуйте, проверьте его, вы увидите, насколько это ясно.

МОЖНО ЛИ РЕШИТЬ, БУДЕТ ЛИ КОД ВЫПОЛНЯТЬСЯ НА ЛОГИЧЕСКОМ ИЛИ ФИЗИЧЕСКОМ Ядро ?

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

Если вы хотите работать на core 3, а не core 1, например, ну, я думаю, что действительно есть некоторые решения, но доступны, только если вы знаете, как кодировать ядро и планировщик ОС, что, я думаю, не так, если вы задаете этот вопрос.

Если вы запускаете 4 CPU-интенсивных процесса на 4 физических / 8 логических процессорах, планировщик будет приписывать каждый из ваших процессов 1 отдельному физическому ядру (и 4 логических ядра останутся не/плохо используются). Но на 4 логических / 8 потоков proc, если единицы обработки(0,1) (1,2) (2,3) (4,5) (5,6) (6,7), тогда не имеет значения, выполняется ли процесс на 0 или 1 : это один и тот же процессор.

из моих знаний по крайней мере (но эксперт может confirm / infirm, возможно, он также отличается от очень конкретных спецификаций оборудования) я думаю, что нет или очень мало разницы между выполнением кода на 0 или 1. В процессоре (0,1) я не уверен, что 0 является логическим, тогда как 1 является физическим, или наоборот. Из моего понимания (которое может быть неправильным), оба являются процессорами из одного и того же процессора, и они просто разделяют свою кэш-память / доступ к оборудованию (включая ОЗУ), а 0-не более физический блок, чем 1.

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

Если вы запускаете 5 CPU-интенсивных задач на логическом ядре 4 physical / 8, поведение будет хаотичным, почти непредсказуемым, в основном зависящим от вашего оборудования и ОС. Планировщик будет стараться изо всех сил. Почти каждый раз вам придется сталкиваться с действительно плохими выступлениями.

это 99% уверен, однако (но проверить его на вашем оборудовании, чтобы быть уверенным), что почти любая многопроцессорная программа будет работать медленнее, если вы используете больше физического ядра, чем доступно.

много вещей может вмешаться... Программа, оборудование, состояние ОС, планировщик, который она использует, фрукты, которые вы съели сегодня утром, имя вашей сестры... Если вы сомневаетесь в чем-то, просто отметьте это, там нет другого простого способа увидеть, теряете ли вы выступления или нет. Иногда информатика может быть очень странной.

--> большую часть времени дополнительные логические ядра действительно бесполезны в PYTHON (но не всегда)

есть 2 основных способа выполнения действительно параллельных задач в python.

  • многопроцессорная обработка (не может использовать логические ядра)
  • multithreading (смогите принять преимущество логически ядер)

например, чтобы запустить 4 задачи параллельно

--> многопроцессорная обработка создаст 4 разных интерпретатора python. Для каждого из них вы должны запустить интерпретатор python, определить права чтения / записи, определить среду, выделить много памяти и т. д. Скажем так: вы начнете совершенно новый экземпляр программы с 0. Это может занять много времени, поэтому вы должны быть уверены, что эта новая программа будет работать достаточно долго, чтобы она стоит того.

Если ваша программа имеет достаточно работы (скажем, несколько секунд работы, по крайней мере), то, поскольку ОС выделяет процессы, потребляющие процессор, на разных физических ядрах, она работает, и вы можете получить много производительности, что здорово. И поскольку ОС почти всегда позволяет процессам общаться между собой (хотя и медленно), они могут даже обмениваться (немного) данными.

--> многопоточность-другому. В вашем интерпретаторе python он будет просто создайте небольшой объем памяти, который будет доступен многим процессорам, и работайте над ним одновременно. Это намного быстрее нерест (где нерест нового процесса на старом компьютере может занять много секунд, иногда нерест потока выполняется в течение смехотворно малой доли времени). Вы не создаете новые процессы, а "потоки", которые намного легче.

потоки могут делиться памятью между потоками очень быстро, потому что они буквально работают вместе на одной памяти (в то время как он должен быть скопирован/обменен при работе с различными процессами).

НО: ПОЧЕМУ МЫ НЕ МОЖЕМ ИСПОЛЬЗОВАТЬ МНОГОПОТОЧНОСТЬ В БОЛЬШИНСТВЕ СИТУАЦИЙ ? ЭТО ВЫГЛЯДИТ ОЧЕНЬ УДОБНО ?

в python есть очень большое ограничение: только одна строка python может быть выполнена одновременно в интерпретаторе python, который называется Gil (Global Interpreter Lock). Поэтому большую часть времени вы даже потеряете производительность, используя многопоточность, потому что разные потоки должны будут ждать доступ к тому же ресурсу. Многопоточность всегда бесполезна и даже хуже, если ваш код является чистым python.

--> ПОЧЕМУ Я НЕ ДОЛЖЕН ИСПОЛЬЗОВАТЬ ЛОГИЧЕСКИЕ ЯДРА ПРИ ИСПОЛЬЗОВАНИИ МНОГОПРОЦЕССОРНОЙ ОБРАБОТКИ ?

логические ядра не имеют собственного доступа к памяти. Они могут работать только на доступ к памяти и на кэш его хостинга физического процессора. Например, очень вероятно (и часто используется), что логическое и физическое ядро одного и того же процессора используют одна и та же функция C/C++ на разных местах кэш-памяти одновременно. Что делает лечение намного быстрее.

но... это функции C / C++! Python-это большая оболочка C / C++, которая требует гораздо больше памяти и процессора, чем эквивалентный код c++. Очень вероятно, что в 2018 году, что бы вы ни хотели сделать, 2 больших процесса python потребуют гораздо больше памяти и чтения/записи кэша, чем может позволить себе один физический+логический блок, и гораздо больше, чем то, что эквивалентный C / C++ действительно-многопоточный код будет потреблять. Это еще раз, почти всегда приведет к снижению производительности. Помните,что каждая переменная, которая недоступна в кэше процессора, займет время X1000 для чтения в памяти. Если ваш кэш уже полностью заполнен для 1 одного процесса python, угадайте, что произойдет, если вы заставите 2 процесса использовать его: они будут использовать его по одному в то время и переключаться постоянно, заставляя данные глупо спускаться и перечитываться каждый раз свич. Когда данные читаются или записываются из памяти, вы можете подумать, что ваш процессор "работает", но это не так. Он ждет данных ! Ничего не делая.

--> КАК ВЫ МОЖЕТЕ ВОСПОЛЬЗОВАТЬСЯ ЛОГИЧЕСКИМИ ЯДРАМИ ?

Как я уже сказал, нет истинной многопоточности (поэтому нет истинного использования логических ядер) в python по умолчанию из-за глобальной блокировки интерпретатора. Вы можете заставить GIL быть удалены во время некоторых частей программы, но я думаю, что это было бы разумно посоветовать вам не прикасаться к нему, если вы точно не знаете, что делаете.

удаление GIL определенно было предметом многих исследований (см. экспериментальные проекты PyPy или Cython, которые оба пытаются это сделать).

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

есть, я признаю, другое решение, которое может работать: - Код функции в C - Оберните его в python с помощью ctype - Используйте модуль многопоточности python для вызова вашей обернутой функции C

это будет работать на 100%, и вы сможете использовать все логические ядра, в python, с многопоточностью и по-настоящему. GIL не будет беспокоить вас, потому что вы не будете выполнять истинные функции python, а вместо этого функции C.

например, некоторые библиотеки, такие как Numpy, могут работать со всеми доступными потоками, потому что они закодированы в C. Но если Вы дойдете до этого момента, я всегда думал, что было бы разумно подумать о выполнении вашей программы на C/C++ напрямую, потому что это очень далеко от первоначального питонического духа.

* * --> НЕ ВСЕГДА ИСПОЛЬЗУЙТЕ ВСЕ ДОСТУПНЫЕ ФИЗИЧЕСКИЕ ЯДРА **

Я часто вижу, как люди говорят: "Хорошо, у меня есть 8 физических ядер, поэтому я возьму 8 ядер для своей работы". Это часто работает, но иногда оказывается плохой идеей, особенно если ваша работа требует много ввода-вывода.

попробуйте с ядрами N-1 (еще раз, особенно для высокозатратных задач ввода-вывода), и вы увидите, что 100% времени, на каждой задаче/в среднем, одиночные задачи всегда будут работать быстрее на ядре N-1. Действительно, ваш компьютер делает много разных вещей: USB, мышь, клавиатура, сеть, жесткий диск и т. д... Даже на рабочей станции периодические задачи выполняются в любое время в фоновом режиме, о котором вы понятия не имеете. Если вы не позволите 1 физическому ядру управлять этими задачами, ваш расчет будет регулярно прерываться (вымывается из памяти / заменяется обратно в память), что также может привести к вопросам производительности.

вы можете подумать: "ну, фоновые задачи будут использовать только 5% времени процессора, поэтому осталось 95%". Но это не так.

процессор обрабатывает одну задачу за раз. И каждый раз, когда он переключается, значительное количество времени тратится впустую, чтобы вернуть все на свое место в кэше/реестрах памяти. Затем, если по какой-то странной причине планировщик ОС делает это переключение слишком часто (что-то у вас нет контроля), все это вычислительное время потеряна навсегда, и ты ничего не можешь с этим поделать.

если (и это иногда случается) по какой-то неизвестной причине эта проблема планировщика влияет на выполнение не 1, а 30 задач, это может привести к действительно интригующим ситуациям, когда работа над 29/30 физическим ядром может быть значительно быстрее, чем на 30/30

БОЛЬШЕ CPU НЕ ВСЕГДА ЛУЧШИЙ

это очень часто, когда вы используете многопроцессорную обработку.Пул, для использования многопроцессорной обработки.Очередь или очередь менеджера, разделяемая между процессами, чтобы обеспечить базовую связь между ними. Иногда (я, должно быть, сказал 100 раз, но я повторяю это), аппаратно-зависимым образом, это может произойти (но вы должны проверить его для своего конкретного приложения, реализации кода и вашего оборудования), что использование большего количества ЦП может создать узкое место, когда вы делаете процессы общаться / синхронизировать. В этих конкретных случаях может быть интересно запустить на более низком номере процессора или даже попытаться депортировать задача синхронизации на более быстром процессоре (здесь я говорю о научных интенсивные вычисления на кластер, конечно). Поскольку многопроцессорная обработка часто предназначена для использования в кластерах, вы должны заметить, что кластеры часто разогнаны по частоте для энергосберегающих целей. Из-за этого одноядерные выступления могут быть действительно плохо (сбалансировано-гораздо большее количество процессоров), что делает проблему еще хуже, когда вы масштабируете свой код с локального компьютера (несколько ядра, высокая одноядерная производительность) в кластер (много ядер, более низкая одноядерная производительность), потому что ваше узкое место кода в соответствии с соотношением single_core_perf/nb_cpu, что иногда очень раздражает

у всех есть соблазн использовать как можно больше CPU. Но контрольные показатели для этих случаев обязательны.

типичным случаем (в data science for ex) является параллельное выполнение N процессов, и вы хотите суммировать результаты в одном файле. Потому что ты нельзя ждать, пока работа будет выполнена, Вы делаете это через определенный процесс записи. Писатель запишет в выходной файл все, что выталкивается в его многопроцессорной обработке.Очередь (одноядерный и жесткий диск ограниченный процесс). N процессов заполняют многопроцессорную обработку.Очередь.

легко представить, что если у вас есть 31 CPU записи информации на один очень медленный процессор, то ваши выступления упадут (и, возможно, что-то рухнет, если вы преодолеете способность системы обрабатывать временные данные)

--> возьмите домой сообщение

  • использовать psutil для подсчета логических/физических процессоров, а не многопроцессорность.cpu_count() или вообще
  • многопроцессорная обработка может работать только на физическом ядре (или, по крайней мере, проверить его, чтобы доказать, что это не так в вашем случае)
  • многопоточность будет работать на логическом ядре, но вам придется закодировать и обернуть свои функции в C или удалить интерпретатор глобальной блокировки (и каждый раз сделаешь так, один котенок зверски умрет где-нибудь в мире)
  • если вы пытаетесь запустить многопоточность на чистом коде python, у вас будет огромное снижение производительности, поэтому вы должны 99% времени использовать многопоточность вместо
  • если ваши процессы / потоки не имеют длительных пауз, которые вы можете использовать, никогда не используйте больше ядра, чем доступно, и тест правильно, если вы хотите попробовать
  • если ваша задача интенсивна ввода-вывода, вы должны позволить 1 физическому ядру обрабатывать ввод-вывод, и если у вас достаточно физического ядра, оно того стоит. Для многопроцессорных реализаций необходимо использовать N-1 физическое ядро. Для классической двусторонней многопоточности это означает использование N-2 логического ядра.
  • Если вам нужно больше выступлений, попробуйте PyPy (не готов к производству) или Cython, или даже закодировать его в C

последнее, но не менее важно, и самое главное: если вы действительно ищете производительность, вы должны абсолютно, всегда, всегда бенчмарк, а не гадать ничего. Benchmark часто показывает странное поведение платформы / оборудования / драйвера, о котором вы понятия не имеете.


Я нашел решение, которое не включает в себя изменение исходного кода модуля python. Он использует предложенный подход здесь. Это можно проверить только физические ядра активны после запуска этого скрипта, выполнив:

lscpu

в bash возвращает:

CPU(s):                8
On-line CPU(s) list:   0,2,4,6
Off-line CPU(s) list:  1,3,5,7
Thread(s) per core:    1

[можно запустить скрипт, связанный выше, изнутри python]. В любом случае, после запуска сценария выше, введите эти команды в python:

import multiprocessing
multiprocessing.cpu_count()

возвращает 4.