데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [Python] 고성능 Python을 위한 Cython 활용하기 2편

      Teus 23.04.11
      5,214 18 0

      안녕하세요. 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


      <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 연재 마지막 입니다!)


      감사합니다!

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      Teus 님의 최신 블로그

      더보기