[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
"""

image.png

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을 기입하지 않은 경우>

image.png

 

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

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

image.png

 

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를 통해서 바로 확인할 수가 있습니다.

 

image.png

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

 

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
87 [python 일반] 파이썬에서 도커 사용하기: 기초부터 배포까지 졸리운_곰 2024.12.01 410
86 [python 일반] 파이썬 디컴파일 방지 난독화 초간단 방법 file 졸리운_곰 2024.08.13 539
85 [python 일반] [Python] python 예제 - 프로그램 실행 위치 확인 방법 3종 - os.getcwd,... 졸리운_곰 2024.07.28 451
84 [python 일반] [Python] How to capture output of Python's interpreter and show in a Text widget? 졸리운_곰 2024.07.26 230
83 [python 일반] [Python] How to Fix Python’s “List Index Out of Range” Error in For Loops 졸리운_곰 2024.07.26 284
82 [python 일반] [Python] Python 리스트에 특정 값이 있는지 체크하기 졸리운_곰 2024.07.26 418
81 [python 일반] [Python] 원하는 시간마다 파이썬 자동 실행 file 졸리운_곰 2024.07.25 337
80 [python 일반] [Python] pyinstaller] PyInstaller -i, --add-data로 아이콘(icon)을 포함하자 file 졸리운_곰 2024.07.19 428
79 [python 일반] [Python] pyinstaller : 파이썬 컴파일 시 코드 암호화 & 복호화 file 졸리운_곰 2024.06.10 374
78 [python 일반] ** Date형식을 String형식으로 변환 ** 졸리운_곰 2024.06.08 297
77 [python 일반] [Python] strftime과 strptime file 졸리운_곰 2024.06.07 377
76 [python 일반] How to Run a Periodic Background Task in Python 졸리운_곰 2024.06.07 324
75 [python 일반] *win32ctypes.pywin32.pywintypes.error when using pyinstaller in VS Code - Possible Virus/Trojan? 졸리운_곰 2024.06.07 453
74 [python 일반] 【Python】The 'pathlib' package is an obsolete backport of a standard library ・・・ 졸리운_곰 2024.05.30 318
73 [Python 일반] 파이썬 exe 파일 만들기 졸리운_곰 2024.05.30 307
72 [Python] 클립보드 무엇인가? Pyperclip을 통한 자동화 file 졸리운_곰 2024.05.06 364
71 [Python] PyQt와 PySide에 대한 잡설 file 졸리운_곰 2024.04.25 253
70 [python 일반] How to Update All Python Packages : 설치된 파이썬 패키지 모두 업데이트 하기 file 졸리운_곰 2024.03.16 604
69 [python, C++] Interfacing C++ and Python with the Python API : C++ 및 Python과 Python API의 인터페이스 file 졸리운_곰 2023.08.18 302
68 [python 일반] 파이참 에러 : Fatal Python error: init_stdio_encoding: failed to get the Python codec name of the stdio encoding file 졸리운_곰 2023.07.06 354
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED