- 전체
- Python 일반
- Python 수학
- Python 그래픽
- Python 자료구조
- Python 인공지능
- Python 인터넷
- Python SAGE
- wxPython
- TkInter
- iPython
- wxPython
- pyQT
- Jython
- django
- flask
- blender python scripting
- python for minecraft
- Python 데이터 분석
- Python RPA
- cython
- PyCharm
- pySide
- kivy (python)
cython [Cython][Python] 고성능 Python을 위한 Cython 활용하기 2편
2024.06.10 17:40
[Cython][Python] 고성능 Python을 위한 Cython 활용하기 2편
안녕하세요. Teus입니다.
지난 포스팅을 통해서 Cython을 사용하는 방법과 Cython이 어떻게 이뤄졌는지 확인 했습니다.
하지만, Python의 List와 같은 Array 데이터를 활용하는데 문제점을 확인 했습니다.
그래서
이 문제점을 해결하기 위해서 numpy Array를 Cython에서 활용하는 방법을 알아보도록 하겠습니다.
출처 : Cython numpy 활용 공식문서
Cython에서 Numpy 사용하기
cython에서 numpy를 사용하기 위해서는 .pyx 파일에서 역시 numpy를 import해야 합니다.
하지만 이때 cython은 독특하게
아래처럼 여러가지 부가작업이 필요합니다.
%%cython
#cython에서 numpy를 사용하기 위해서 아래 3개를 import 해야됨
import numpy as np
cimport numpy as np
np.import_array()
def np_foo(object f):
print(f)
temp = np.array([i for i in range(100)])
np_foo(temp)
이때 numpy의 add, mul연산 처럼 CPython Level로 정의된 연산의 경우
cython을 사용하나, python에서 실행하나 큰 차이가 없습니다.
%%cython
import numpy as np
cimport numpy as np
np.import_array()
def cython_np_add(np.ndarray f):
for _ in range(10000):
f += 5
return f
def python_np_add(f):
for _ in range(10000):
f += 5
return f
import time
temp = np.array([i for i in range(4000000)])
st = time.time()
cython_np_add(temp)
print(f"cython time : {time.time()-st}")
st = time.time()
python_np_add(temp)
print(f"python time : {time.time()-st}")
'''
cython time : 16.287737607955933
python time : 16.263918161392212
'''
하지만 cython을 통해서 numpy array의 데이터에 index로 접근할 경우 결과가 달라지게 됩니다.
%%cython
import numpy as np
cimport numpy as np
np.import_array()
def cython_reduce_sum_with_numpy(np.ndarray f):
cdef long long i = 0;
cdef long long ret = 0;
cdef long long data_len = f.shape[0];
for i in range(data_len):
ret += f[i]
return ret
def python_reduce_sum_with_numpy(f):
ret = 0
for i in range(len(f)):
ret += f[i]
return ret
import time
temp = np.array([i for i in range(100000000)])
st = time.time()
cython_reduce_sum_with_numpy(temp)
print(f"cython time : {time.time()-st}")
st = time.time()
python_reduce_sum_with_numpy(temp)
print(f"python time : {time.time()-st}")
"""
cython time : 8.304328441619873
python time : 4.437905550003052
"""

Cython을 사용한게 오히려 Python보다 두배는 느려지는 결과를 얻게 됩니다.
Cython에서 Numpy 최적화 하기1
cython의 경우 numpy array의 Data Type과 ndim을 입력하지 않을 경우 [] operator를 통한 indexing이 Python Operation으로 처리되게 됩니다.
때문에, Cython과 Python Operation이 번갈아 가면서 오히려 성능이 저하됩니다.
이때 공식문서의 안내에 따라 np.ndarray의 Data Type과 ndim을 입력할 경우 놀라운 속도 향상을 확인할 수가 있습니다.
%%cython
import numpy as np
cimport numpy as np
np.import_array()
#cython의 매개변수에서 활용하기 위한 ctypedef
ctypedef np.int_t DTYPE_t
#매개변수로 받은 np.ndarray의 Data Type과 ndim을 명기해줌
def cython_reduce_sum_with_numpy(np.ndarray[DTYPE_t, ndim = 1] f):
cdef long long i = 0;
cdef long long ret = 0;
cdef long long data_len = f.shape[0];
for i in range(data_len):
ret += f[i]
return ret
def python_reduce_sum_with_numpy(f):
ret = 0
for i in range(len(f)):
ret += f[i]
return ret
import time
temp = np.array([2 for i in range(100000000)])
st = time.time()
cython_reduce_sum_with_numpy(temp)
print(f"cython time : {time.time()-st}")
st = time.time()
python_reduce_sum_with_numpy(temp)
print(f"python time : {time.time()-st}")
'''
cython time : 0.07773971557617188
python time : 3.8791370391845703
'''
위 두 코드를 실행하며, annotate를 추가하여 확인하면 Data Type과 ndim을 명시한 뒤에 python operation이 줄어드는 것을 확인할 수가 있습니다.
(add과정에서 Py_GetItem이 사용되며, 주기적으로 변수의 Reference Counter를 조정하는것을 볼 수 있습니다)
<Data Type과 ndim을 기입하지 않은 경우>

<Data Type과 ndim을 기입한 경우>

Cython에서 Numpy 최적화 하기2
여기서 추가적인 성능 향상이 가능합니다.
공식문서에서는 Decorator를 통해서 반복문의 bound check와 minus index에대한 고려사항을 제외시킬 수가 있습니다.
아래 코드를 보겠습니다.
%%cython --annotate
import numpy as np
cimport numpy as np
np.import_array()
DTYPE = np.int_
ctypedef np.int_t DTYPE_t
cimport cython
@cython.boundscheck(False) # turn off bounds-checking for entire function
@cython.wraparound(False) # turn off negative index wrapping for entire function
def cython_reduce_sum_with_opt_numpy(np.ndarray[DTYPE_t, ndim = 1] f):
cdef long long i = 0;
cdef long long ret = 0;
cdef long long data_len = f.shape[0];
for i in range(data_len):
ret += f[i]
return ret
def cython_reduce_sum_with_numpy(np.ndarray[DTYPE_t, ndim = 1] f):
cdef long long i = 0;
cdef long long ret = 0;
cdef long long data_len = f.shape[0];
for i in range(data_len):
ret += f[i]
return ret
import time
temp = np.array([2 for i in range(100000000)])
st = time.time()
cython_reduce_sum_with_numpy(temp)
print(f"cython time : {time.time()-st}")
st = time.time()
cython_reduce_sum_with_opt_numpy(temp)
print(f"cython opt time : {time.time()-st}")
'''
cython time : 0.0887763500213623
cython opt time : 0.03389263153076172
'''
코드 실행 결과를 보면 최적화 전 대비 2배 이상 빠른속도를 보여줍니다.
이 차이점은 annotate를 통해서 바로 확인할 수가 있습니다.

CPython으로 변환된 코드를 보면
boundcheck와 minus index를 고려하지 않게 되면서 더이상 Python Operation이 없이 동작하는것을 알 수가 있습니다.
덕분에 python에서 경험할 수 없는 빠른 속도로 결과를 얻어낼 수가 있습니다.
(단, bound check가 사라지기 때문에 데이터 오염 또는 프로그램 crash가 일어날 수가 있습니다)
더 빠른 Python를 위해서
Cython 1, 2편을 통해서 어떻게 Python의 데이터를 Cython 함수에 보내고, 활용할 수 있는지 확인하였습니다.
다음 포스팅에서는 Cython 함수에서 Numpy Array를 받고
Array를 Python의 GIL없이 멀티쓰레드를 사용하여 처리하는 방법에 대해서 다루도록 하겠습니다.
(다음편이 Cython 연재 마지막 입니다!)
감사합니다!
[출처] https://devocean.sk.com/blog/techBoardDetail.do?ID=164605
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
댓글 0
| 번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
|---|---|---|---|---|
| 3 |
wxpython_in_action_wxact.pdf
| 졸리운_곰 | 2014.10.14 | 7113 |
| 2 |
wxPython 2.8 Application Development Cookbook (2010).pdf
| 졸리운_곰 | 2014.10.14 | 2773 |
| 1 |
The wxPython tutorial.pdf
| 졸리운_곰 | 2014.10.14 | 2875 |

