Странная ошибка в Pandas и Numpy в отношении многопоточности

25

Большая часть функции Numpy по умолчанию включает многопоточность.

например, я работаю на 8-ядерном компьютере Intel Cpu, если я запускаю скрипт

import numpy as np    
x=np.random.random(1000000)
for i in range(100000):
    np.sqrt(x)

Linux topпокажет 800% загрузки процессора во время работы, введите описание изображения здесь что означает, что numpy автоматически обнаруживает, что моя рабочая станция имеет 8 ядер, и np.sqrtавтоматически использует все 8 ядер для ускорения вычислений.

Однако я обнаружил странную ошибку. Если я запускаю скрипт

import numpy as np
import pandas as pd
df=pd.DataFrame(np.random.random((10,10)))
df+df
x=np.random.random(1000000)
for i in range(100000):
    np.sqrt(x)

использование процессора составляет 100%! Это означает, что если вы добавите две панды DataFrame перед запуском любой функции numpy, функция автоматической многопоточности numpy пропадет без предупреждения! Это абсолютно не разумно, почему расчет Pandas dataFrame влияет на настройку потоков Numpy? Это ошибка? Как обойти это?введите описание изображения здесь


PS:

Я копаю дальше, используя perfинструмент Linux .

работает первый скрипт показывает

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

Во время работы второй скрипт показывает

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

Таким образом, оба сценария включают в себя libmkl_vml_avx2.so, в то время как первый сценарий включает дополнительный, libiomp5.soкоторый, похоже, связан с openMP.

А так как vml означает библиотеку математики для векторов Intel, то, согласно vml doc, я предполагаю, что по крайней мере ниже функции автоматически являются многопоточными.

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

user15964
источник
Я не уверен, что понимаю ваш вопрос. Можете ли вы уточнить?
AMC
@AMC Я обновил свой пост, надеюсь, теперь он понятен
user15964
Я думаю, что нужна дополнительная информация, такая как np, pandas, версия, процессор, тип ОС ... Я не могу воспроизвести на моей машине. Он не использует несколько процессоров в обоих кодах.
Hunzter
@hunzter Хорошо, вот информация: Ubuntu 16.04.5 LTS numpy 1.17.2 py37haad9e8e_0 pandas 0.25.1 py37he6710b0_0 Процессор Intel (R) Xeon (R) E5-1680 v4 @ 3.40 ГГц. PS. Я использую анаконду
user15964
1
Не могли бы вы проверить это:import numpy as np import pandas as pd import os os.environ["MKL_NUM_THREADS"] = '4' print(os.environ["MKL_NUM_THREADS"]) df=pd.DataFrame(np.random.random((10,10))) df+df print(os.environ["MKL_NUM_THREADS"]) a = np.random.random((20000000, 3)) b = np.random.random((3, 30)) for _ in range(10): c = np.dot(a, b)
Стас Бузулук

Ответы:

13

Pandas использует внутреннюю numexprчасть для вычисления некоторых операций и numexprустанавливает максимальное количество потоков для vml равным 1, когда оно импортируется :

# The default for VML is 1 thread (see #39)
set_vml_num_threads(1)

и он импортируется пандами, когда df+dfоценивается в expressions.py :

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

Тем не менее, распределение Анаконда также использует VML-функциональность для таких функций , как sqrt, sin, cosи так далее - и один раз numexprустановить максимальное число VML-нитей в 1, Numpy-функции больше не использовать распараллеливание.

Проблему легко увидеть в gdb (используя ваш медленный скрипт):

>>> gdb --args python slow.py
(gdb) b mkl_serv_domain_set_num_threads
function "mkl_serv_domain_set_num_threads" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (mkl_serv_domain_set_num_threads) pending.
(gbd) run
Thread 1 "python" hit Breakpoint 1, 0x00007fffee65cd70 in mkl_serv_domain_set_num_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
(gdb) bt 
#0  0x00007fffee65cd70 in mkl_serv_domain_set_num_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
#1  0x00007fffe978026c in _set_vml_num_threads(_object*, _object*) () from /home/ed/anaconda37/lib/python3.7/site-packages/numexpr/interpreter.cpython-37m-x86_64-linux-gnu.so
#2  0x00005555556cd660 in _PyMethodDef_RawFastCallKeywords () at /tmp/build/80754af9/python_1553721932202/work/Objects/call.c:694
...
(gdb) print $rdi
$1 = 1

то есть, мы видим, numexprустанавливает количество потоков равным 1. Что позже используется при вызове функции vml-sqrt:

(gbd) b mkl_serv_domain_get_max_threads
Breakpoint 2 at 0x7fffee65a900
(gdb) (gdb) c
Continuing.

Thread 1 "python" hit Breakpoint 2, 0x00007fffee65a900 in mkl_serv_domain_get_max_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
(gdb) bt
#0  0x00007fffee65a900 in mkl_serv_domain_get_max_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
#1  0x00007ffff01fcea9 in mkl_vml_serv_threader_d_1i_1o () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
#2  0x00007fffedf78563 in vdSqrt () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_lp64.so
#3  0x00007ffff5ac04ac in trivial_two_operand_loop () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/core/_multiarray_umath.cpython-37m-x86_64-linux-gnu.so

Таким образом, мы можем видеть, что numpy использует реализацию vml, vdSqrtкоторая использует, mkl_vml_serv_threader_d_1i_1oчтобы решить, следует ли выполнять вычисления параллельно, и выглядит количество потоков:

(gdb) fin
Run till exit from #0  0x00007fffee65a900 in mkl_serv_domain_get_max_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
0x00007ffff01fcea9 in mkl_vml_serv_threader_d_1i_1o () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
(gdb) print $rax
$2 = 1

регистр %raxимеет максимальное количество потоков и равен 1.

Теперь мы можем использовать numexprдля увеличения количества vml-потоков , то есть:

import numpy as np
import numexpr as ne
import pandas as pd
df=pd.DataFrame(np.random.random((10,10)))
df+df

#HERE: reset number of vml-threads
ne.set_vml_num_threads(8)

x=np.random.random(1000000)
for i in range(10000):
    np.sqrt(x)     # now in parallel

Теперь используются несколько ядер!

Свинец
источник
Спасибо огромное! Наконец, отличный ответ объясняет все. В конце концов, это numexprза кулисами.
user15964
Договорились .. хорошо копать! Следующий вопрос, хотя .. почему NumberxPr подтолкнуть поток считать до 1? Возможно из-за нестабильности / проблем с потоками? Вместо того, чтобы возвращать счет до 8, было бы безопаснее перейти к поточно-безопасной / стабильной версии NumPy. Может быть, также полезно проверить эту переменную с последним и лучшим NumPy в случае, если это больше не нужно, поэтому технически это ошибка.
Эндрю Атренс
@AndrewAtrens вы можете посмотреть на github.com/pydata/numexpr/issues/39 и github.com/pydata/numexpr/issues/355
EAD
2

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

http://numpy-discussion.10968.n7.nabble.com/ANN-NumExpr-2-7-0-Release-td47414.html

Мне нужно разобраться, как это приклеено к интерпретатору python, учитывая ваш пример кода, где кажется, что он как-то позволяет нескольким явно синхронным / упорядоченным вызовам np.sqrt () выполняться параллельно. Я предполагаю, что если интерпретатор Python всегда просто возвращает ссылку на объект, когда он выталкивает стек, а в вашем примере это просто передача этих ссылок, а не назначение или манипулирование ими каким-либо образом, это было бы хорошо. Но если последующие итерации цикла зависят от предыдущих, тогда кажется менее ясным, как их можно безопасно распараллелить. Возможно бесшумный отказ / неправильные результаты - результат хуже, чем сбои.

Эндрю Атренс
источник
Привет, Эндрю Атренс, Ты почти у цели. Это проблема ne.set_vml_num_threads (). Большое спасибо за ваше время, посвященное моей проблеме.
user15964
Счастливые Следы :)
Эндрю Атренс
0

Я думаю, что ваша первоначальная предпосылка может быть неправильной -

Вы заявили: это означает, что numpy автоматически обнаруживает, что моя рабочая станция имеет 8 ядер, а np.sqrt автоматически использует все 8 ядер для ускорения вычислений.

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

Сказав это, интерпретатор python может оптимизировать цикл for для параллелизма, что может быть тем, что вы видите, но я сильно подозреваю, что если вы посмотрите на время настенного времени, чтобы этот цикл выполнялся, он не будет отличается независимо от того, используете ли вы (по-видимому) 8 ядер или 1 ядро.

ОБНОВЛЕНИЕ: прочитав немного больше комментариев, кажется, что многоядерное поведение, которое вы видите, связано с распределением anaconda интерпретатора python. Я посмотрел, но не смог найти никакого исходного кода для него, но похоже, что лицензия python позволяет организациям (таким как anaconda.com) компилировать и распространять производные интерпретатора, не требуя публикации их изменений.

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

Также сделайте быструю проверку времени настенных часов с / без оптимизации, чтобы увидеть, действительно ли оно в 8 раз быстрее - даже если у вас действительно работают все 8 ядер вместо 1, было бы полезно узнать, действительно ли результаты 8х. быстрее или если используются спин-блокировки, которые все еще сериализуются на одном мьютексе.

Эндрю Атренс
источник
1
Привет, Эндрю Атренс. Но распараллеливание не выполняется python, оно выполняется бэкэндом Anaconda Numpy, то есть Intel MKL. И да, я открыл вопрос о NumPy, они предложили мне открыть вопрос о Anaconda, и я сделал это. Однако я ни разу не получил ни одного ответа от Анаконды. Так что, возможно, они просто проигнорировали мой отчет ...
user15964
Это проблема с версией / выпуском anaconda интерпретатора python - их версия использует openmp, тогда как стандартная версия python - нет.
Эндрю Атренс
Это проблема с версией / выпуском анаконды интерпретатора python - их версия ссылается на / использует openmp apis, тогда как стандартная интерпретация релиза python - нет. когда я говорю об использовании, я буквально имею в виду вызов функции openmp api «под капотом». Как и в случае любой неявной оптимизации, когда мы не можем увидеть исходный код, мы можем только сообщить о нем (как у вас) и, если возможно, попытаться обойти его.
Эндрю Атренс
Еще одна мысль об этом ... вы могли бы перекодировать свое приложение, чтобы явно использовать многопоточные библиотеки Python, а не полагаться на оптимизатор интерпретатора, чтобы сделать это за вас ... Я думаю о пулах потоков ... в зависимости от сложности вашего приложения, и если это не ваш первый шаг в многопоточном программировании, это может быть не так уж и сложно. Чтобы поддерживать переносимость, вероятно, следует попытаться избежать чего-то специфичного для anaconda или openmp - я оставлю это вам, поскольку у меня нет времени копаться в этом ... :) В любом случае, удачи и надеемся, что это помогает избавиться от тумана, что вы видите. :) :)
Эндрю Атренс