Что я устанавливаю, когда ограничиваю количество потоков?

У меня есть несколько большой код, который использует библиотеки numpy, scipy, sklearn, matplotlib. Мне нужно ограничить использование ЦП, чтобы он не потреблял всю доступную вычислительную мощность в моем вычислительном кластере. Следуя этому ответу, я реализовал следующий блок кода, который выполняется сразу после запуска скрипта:

import os
parallel_procs = "4"
os.environ["OMP_NUM_THREADS"] = parallel_procs
os.environ["MKL_NUM_THREADS"] = parallel_procs
os.environ["OPENBLAS_NUM_THREADS"] = parallel_procs
os.environ["VECLIB_MAXIMUM_THREADS"] = parallel_procs
os.environ["NUMEXPR_NUM_THREADS"] = parallel_procs

Насколько я понимаю, это должно ограничить количество используемых ядер до 4, но, видимо, этого не происходит. Вот что показывает htop для моего пользователя и этого скрипта:

введите здесь описание изображения

Есть 16 процессов, 4 из которых показывают процент загрузки процессора выше 100%. Это отрывок из lscpu:

CPU(s):              48
On-line CPU(s) list: 0-47
Thread(s) per core:  2
Core(s) per socket:  12
Socket(s):           2

Я также использую библиотеку multiprocessing в своем коде. Я установил такое же количество процессов, используя multiprocessing.Pool(processes=4). Без блока кода, показанного выше, скрипт настаивал на использовании как можно большего количества ядер, по-видимому, полностью игнорируя multiprocessing.

Тогда у меня возникают вопросы: что я ограничиваю, когда использую приведенный выше код? Как мне интерпретировать вывод htop?


person Gabriel    schedule 13.05.2021    source источник
comment
Является ли ваш код Python многопоточным или преднамеренно использует несколько процессов (как в случае с модулем multiprocessing)? Переменные среды, которые вы устанавливаете, скорее всего, повлияют только на код библиотеки, который может использовать потоки внутри. Однако, если вы используете свою собственную многопоточность, вы все равно можете вызывать эти библиотеки несколько раз параллельно.   -  person Blckknght    schedule 13.05.2021
comment
Я действительно использую библиотеку multiprocessing в своем коде. Я установил такое же количество процессов, используя multiprocessing.Pool(processes=4). Мне пришлось добавить блок кода, показанный в вопросе, потому что в противном случае скрипт настаивал на использовании как можно большего количества ядер, по-видимому, игнорируя multiprocessing.   -  person Gabriel    schedule 13.05.2021
comment
Общее количество используемых ядер, вероятно, является произведением количества процессов и количества потоков (например, 4*4). Код библиотеки не будет знать о коде multiprocessing (или о коде другой библиотеки, который может работать параллельно). Если библиотеки, которые вы используете, выполняют свои собственные потоки, вам может не понадобиться добавлять собственную параллельную обработку.   -  person Blckknght    schedule 13.05.2021
comment
Это похоже на недавний вопрос: stackoverflow.com/questions/67474542/   -  person Jérôme Richard    schedule 13.05.2021


Ответы (1)


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

У меня была аналогичная проблема при многопроцессорной обработке частей моего кода. Библиотеки numpy/scipy, по-видимому, запускают дополнительные потоки, когда вы выполняете векторизованные операции, если вы скомпилировали библиотеки с помощью BLAS или MKL (или если репозиторий conda, из которого вы их извлекли, также включал библиотеку BLAS/MKL), для ускорения определенных вычислений.

Это нормально при запуске вашего скрипта в одном процессе, так как он будет создавать потоки до числа, указанного OPENBLAS_NUM_THREADS или MKL_NUM_THREADS (в зависимости от того, есть ли у вас библиотека BLAS или библиотека MKL — вы можете определить, используя numpy.__config__.show()), но если вы явно используете multiprocesing.Pool, тогда вы, вероятно, хотите контролировать количество процессов в multiprocessing - в этом случае имеет смысл установить n=1 (до импорта numpy и scipy) или какое-то небольшое число для убедитесь, что вы не переполняете подписку:

n = '1'
os.environ["OMP_NUM_THREADS"] = n
os.environ["MKL_NUM_THREADS"] = n

Если вы установите multiprocessing.Pool(processes=4), будет использоваться 4*n процессов (n потоков в каждом процессе). В вашем случае кажется, что у вас есть пул из 4 процессов, и каждый из них запускает по 4 потока, следовательно, 16 процессов python.

Выход htop дает 100 % при условии, что на каждое ядро ​​приходится один ЦП. Поскольку машина с Linux интерпретирует поток как ЦП (здесь я могу ошибаться в терминологии), если у вас 4 потока на ЦП, это означает, что полная загрузка фактически составляет 400%. Это может быть не максимально, в зависимости от выполняемых операций (и от кэширования, поскольку ваша машина выглядит гиперпоточной).

Поэтому, если вы выполняете операцию numpy/scipy в частях кода, которые находятся в одном процессе/одном потоке, вам лучше установить большее n, но для многопроцессорных разделов может быть лучше установить больший пул и одиночные или маленькие n. К сожалению, вы можете установить это только один раз, в начале вашего скрипта, если вы передаете флаги через флаги среды. Если вы хотите установить его динамически, я видел где-то в обсуждении проблем с numpy, что вы должны использовать threadpoolctl ( Добавлю ссылку, если найду еще).

person Tim Jim    schedule 14.05.2021
comment
Это очень подробный ответ Тим. Спасибо! - person Gabriel; 15.05.2021
comment
Извините, я не могу предоставить лучшие источники - это было немного обучения на собственном опыте, когда мне просто нужно было, чтобы что-то работало. - person Tim Jim; 15.05.2021