[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

 

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
66 [Django] 장고 CORS 크로스 도메인 이슈 졸리운_곰 2023.12.17 504
65 [django] CORS(Cross Origin Resource Sharing) in Django 졸리운_곰 2023.12.17 421
64 [django] Forbidden (CSRF cookie not set.) 오류 해결하기 file 졸리운_곰 2023.12.17 346
63 [Django] Django 서버를 domain forwarding 시 : Django nginx Refused to display in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN' 졸리운_곰 2023.12.17 404
62 [Django] Django SSLServer 설정 및 구동 방법 file 졸리운_곰 2023.12.16 288
61 [Django][Django restframework] Django REST framework 시작하기 file 졸리운_곰 2023.05.07 365
60 [Django] REST API 로그인 서버 만들기 (2) - DB 연동, 테스트 file 졸리운_곰 2023.05.07 803
59 [Django] REST API 로그인 서버 만들기 (1) - 코드 졸리운_곰 2023.05.07 430
58 [Django] user의 ip address 가지고 오기 졸리운_곰 2023.05.07 461
57 [Django] [Python_Django] You are trying to add a non-nullable field '필드명' to post without a default 해결 졸리운_곰 2023.05.07 325
56 [Django] [Python_Django] 관리자 계정에서 테이블 관리하기 file 졸리운_곰 2023.05.06 397
55 [Django] TIP - 장고 데이터베이스 여러개 사용하기 (Django multidatabase) file 졸리운_곰 2023.05.05 345
54 [Django] 장고 동작 flow에 대해 알아보자! file 졸리운_곰 2023.05.01 455
53 [python django] 내 이해를 도울 Django flow file 졸리운_곰 2023.05.01 502
52 [Django] 개발 Flow & 개발 환경 세팅 file 졸리운_곰 2023.05.01 386
51 [Django] 장고 명령어 기초 정리 졸리운_곰 2023.05.01 439
50 [python django] 장고 자주 쓰는 manage.py 명령 졸리운_곰 2023.05.01 610
49 [python][Django] Python Package Trends: Visualize Package Download Stats in Django file 졸리운_곰 2023.03.18 368
48 [python][Django] Django Workflow and Architecture 장고 개발 워크플로우 및 구조 file 졸리운_곰 2023.03.12 338
47 [python][Django] DRF(장고 rest framework)와 REST API서버 개발 file 졸리운_곰 2023.03.12 341
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED