본문 바로가기
카테고리 없음

python+cython+numpy+openmp+pyximport

by DogBull 2018. 3. 3.
샘플 코드 위치:

2차원의 배열(래스터 자료, 이미지 자료)을 numpy 를 이용하여 계산하려고 할 때, if 구문의 처리가 난감할 때가 있다.

평범한 python 구문으로는 이렇게 처리되는 것을,
if a > 10:
    b = 10
elif a > 5:
    b = 20
elif a > 0:
    b = 0
else:
    b = -1


numpy 에서는 이렇게 처리해야 한다.

b = numpy.where(
    a > 10,
    10,
    numpy.where(
        a > 5,
        20,
        numpy.where(
            a > 0,
            0,
            -1
       )
    )
)


속도 희생을 감수하고서라도, numpy.vectorize를 사용하면 다음과 같이 쓸 수 있다.

def func(x):
    if x > 10:
        return 10
    elif a > 5:
        return 20
    elif a > 0:
        return 0
    else:
        return -1

vfunc = numpy.vectorize(func)

b = vfunc(a)

첫 번째 코드 조각은 모든 격자에 대해 각각 계산하는 구문이다.(모든 격자를 순회하는 외부 반복문은 생략되었음)
두 번째 코드 조각은 객체에 대해 직접 계산하는 구문이다.(a, b는 2차원 numpy 배열로서 각종 연산자가 오버로딩 되어 있음)



더욱이 아래와 같은 처리를 하고자 할 때, numpy.where 구문으로의 표현은 훨씬 복잡해 진다.

c = 0
if a > 10:
    b = 10
elif a > 5:
    b = 20
elif a > 0:
    b = 0
else:
    b = -1
    c = -1


numpy.array 를 이용하면서 얻을 수 있는 장점은 많다. 가장 큰 두 가지 장점은 pythonic(유지보수, 확장성 등 python 에 관한 거의 모든 것)과 처리속도(cython, C/C++ 확장 등을 동원해도 numpy 의 속도를 넘지는 못했다. 단 싱글 스레드에서)이다.

numpy.array 의 각 원소(래스터 자료를 다루고 있으므로 '격자')를 cell by cell 로 접근하여 처리해보았는데, 어머어마하게 느린 속도에 깜짝놀랐다. numpy 의 속도에 근접하기 위해서는 여러 가지 단점을 감내한 채로 cython 을 사용해야 하는 것이 그나마 나은 선택이라는 것을 알았지만, 역시 처리 속도는 두 배이상 차이가 났다.

테스트에 사용된 연산식은 보통 많이 사용되는 몇 가지를 골라 임의로 작성하였다.

sin(a)*cos(a)*tan(a)*sqrt(a)*exp(a)*sin(b)*cos(b)*tan(b)*sqrt(b)*exp(b)


a, b 모두 4000 x 4000 크기의 임의의 값으로 채워넣고 위 연산을 적용해본 결과 numpy 는 최고 1.2s(ㅎㄷㄷ) 가 기록되었고 cython 을 최대한(?) 이용한 결과는 최고 2.1s(ㅠ.ㅠ)가 기록되었다. 순수 파이선 반복으로는 183s(@.@)가 기록되었다.

cython 을 사용하면서 발생하는 다양한 불편한 점을 감수하고라도 뭇 프로그래밍 언어가 사용하는 처리 기법(위 예제에서 numpy.where 가 아닌 if 문)을 사용하는 것이 더 나은게 아닌가 싶기도하다.


cython-parallel(cython & openmp) 조합으로 만족할 만한 수준으로 속도를 끌어올릴 수 있었으나, CPU의 모든 코어를 점유하면서까지 이 정도의 속도 향상이 과연 적당한지 의문이다.

E5-2690 V4 x 2ea (28코어 56스레드)로 구동해본 결과 numpy(싱글스레드)는 1.9s, 나름 최적화한 cython&openmp 로는 0.19s 정도가 기록되었다. 56개의 스래드를 전개하고도 10배 향상 밖에 없으니, 뭔가 손해보는 느낌도 있고, 더 다듬어야할 여지가 있는것 같기도 하다.

#01. Shape: 5000x5000

#02. algebra_python
  * elapsed time: 1.9640274047851562

#03. algebra_python_brute_force
  * elapsed time: 282.20939111709595
  * isclose: True

#04. algebra_cython_optimize_0
  * elapsed time: 12.367467880249023
  * isclose: True

#05. algebra_cython_optimize_1
  * elapsed time: 3.5004560947418213
  * isclose: True

#06. algebra_cython_optimize_2
  * elapsed time: 3.542889356613159
  * isclose: True

#07. algebra_cython_optimize_3
  * elapsed time: 1.7143189907073975
  * isclose: True

#08. algebra_cython_optimize_4
  * elapsed time: 0.2050466537475586
  * isclose: True

#09. algebra_cython_optimize_5
  * elapsed time: 0.19355154037475586
  * isclose: True