[python] 멀티 스레딩에 관한 판다와 너 피의 이상한 버그

Numpy의 기능 대부분은 기본적으로 멀티 스레딩을 활성화합니다.

예를 들어 스크립트를 실행하면 8 코어 인텔 CPU 워크 스테이션에서 작업합니다.

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

리눅스 top는 실행 중 800 % CPU 사용량을 보여줍니다.
여기에 이미지 설명을 입력하십시오
즉, 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)

CPU 사용량은 100 %입니다.
그것은 numpy 함수를 실행하기 전에 두 개의 pandas DataFrame을 더하면 numpy의 자동 멀티 스레딩 기능은 경고없이 사라집니다! 이것은 절대적으로 합리적이지 않습니다. 왜 Pandas dataFrame 계산이 Numpy 스레딩 설정에 영향을 미칩니 까? 버그입니까? 이 문제를 해결하는 방법?여기에 이미지 설명을 입력하십시오


추신:

나는 리눅스 perf도구를 사용하여 더 파다 .

첫 스크립트 실행

여기에 이미지 설명을 입력하십시오

두 번째 스크립트 쇼를 실행하는 동안

여기에 이미지 설명을 입력하십시오

따라서 두 스크립트는 모두 포함 libmkl_vml_avx2.so하고 첫 번째 스크립트는 libiomp5.soopenMP와 관련된 추가 스크립트를 포함 합니다.

그리고 vml은 인텔 벡터 수학 라이브러리를 의미하므로 vml doc에 따르면 적어도 아래 함수는 모두 자동으로 멀티 스레드됩니다

여기에 이미지 설명을 입력하십시오



답변

Pandas는 numexpr후드 아래를 사용 하여 일부 작업을 계산하고 numexprvml을 가져올 때 최대 스레드 수를 1로 설정합니다 .

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

그리고 expressions.pydf+df 에서 평가 될 때 팬더가 가져옵니다 .

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

그러나, 아나콘다 분포는 또한 같은 기능 VML-기능을 사용 sqrt, sin, cos등 – 한 번 numexpr1 VML – 스레드의 최대 수를 설정하십시오 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입니다.

이제 vml-threads 수numexpr늘리는 데 사용할 수 있습니다 .

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

이제 여러 코어가 사용됩니다!


답변

numpy를 살펴보면 멀티 스레딩과 관련하여 온 / 오프 문제가있는 것처럼 보이며 사용중인 버전에 따라 ne.set_vml_num_threads ()를 충돌시킬 때 충돌이 발생할 수 있습니다.

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

어떻게 든 np.sqrt ()에 대한 여러 개의 호출을 병렬로 진행하도록 허용하는 것처럼 보이는 코드 예제를 고려할 때 파이썬 인터프리터에 어떻게 붙어 있는지에 대해 알아야합니다. 파이썬 인터프리터가 스택에 팝 될 때 항상 객체에 대한 참조를 반환하는지, 그리고 예제에서 그러한 참조를 지정하고 어떤 식 으로든 할당하거나 조작하지 않는 것이 좋습니다. 그러나 후속 루프 반복이 이전 루프 반복에 의존하는 경우 이러한 루프 반복이 어떻게 안전하게 병렬화 될 수 있는지 명확하지 않은 것 같습니다. 틀림없이 조용한 실패 / 잘못된 결과는 충돌보다 더 나쁜 결과입니다.


답변

귀하의 초기 전제가 잘못되었다고 생각합니다.

numpy는 내 워크 스테이션에 8 개의 코어가 있음을 자동으로 감지하고 np.sqrt는 자동으로 8 개의 코어를 모두 사용하여 계산을 가속화합니다.

단일 함수 np.sqrt ()는 부분적으로 완료되기 전에 다음에 호출되거나 리턴되는 방법을 추측 할 수 없습니다. 파이썬에는 병렬 메커니즘이 있지만 자동적 인 것은 없습니다.

자, 파이썬 인터프리터는 병렬 처리를 위해 for 루프를 최적화 할 수 있다고 말했을 것입니다. 8 개의 코어 또는 1 개의 코어를 사용하는지에 관계없이 다릅니다.

업데이트 : 주석을 조금 더 읽은 것은보고있는 멀티 코어 동작이 파이썬 인터프리터의 아나콘다 배포와 관련이있는 것처럼 보입니다. 나는 살펴 보았지만 소스 코드를 찾을 수는 없었지만 파이썬 라이센스는 anaconda.com과 같은 엔티티가 변경 사항을 게시하지 않고도 인터프리터의 파생물을 컴파일하고 배포 할 수있게합니다.

나는 당신이 아나콘다 사람들에게 연락 할 수 있다고 생각합니다-당신이보고있는 행동은 그들이 통역사에서 무엇을 바꾸 었는지 알지 못하면 파악하기 어려울 것입니다 ..

또한 최적화가 있거나없는 벽 시계 시간을 신속하게 확인하여 실제로 8 배 더 빠르지 않은지 확인하십시오. 실제로 8 개의 코어가 모두 1 대 대신 작동하더라도 결과가 실제로 8 배인지 아는 것이 좋습니다 더 빠르거나 단일 뮤텍스에서 직렬화되는 스핀 락이있는 경우.


답변