사이썬

사이썬(Cython)은 CPython 확장 모듈을 손쉽게 생성하도록 고안된 컴파일 언어이다. 파이썬 문법을 기반으로 C/C++ 루틴을 호출을 위한 외부 함수 인터페이스와 실행 속도 향상을 위한 정적 형 지정 등이 추가된 형태를 하고 있다. 이러한 특징은 파이썬의 빠른 생산성을 유지하면서도 외부 C 라이브러리와 간결하게 연동하거나 실행 속도 향상 할 수 있도록 해준다.
역사.
사이썬은 파이랙스(Pyrex)로부터 분기되어 나온 언어이다. 파이랙스는 스크립트 방식인 파이썬의 소스 코드를 컴파일하기 위해 만들어진 초기 프로젝트이다. 사이썬은 세이지(Sage) 프로젝트 내부에서 파이랙스를 고도화하기 위해 개발되었으나, 사이썬 자체에 대한 관심도가 높아짐에 따라 별도의 프로젝트로 분리되었다. 처음에는 소스 코드의 컴파일을 통해 계산용 환경인 세이지의 수행 속도를 향상시키기 위해 사용되었으며, 컴파일러의 내부적 명칭은 세이지-엑스(Sage-X)로 불리었다. 세이지 개발자들은 파이썬 컴파일러를 사용하기 위한 목적으로 세이지를 다운로드하는 횟수가 급증하자 이를 별도의 프로젝트로 만들었다. 이렇게 세이지에서 분리되어 나와 파이랙스 개발자들과 공동으로 프로젝트가 진행되면서 지금의 사이썬이라는 명칭으로 불리게 되었다.
원리.
사이썬은 파이썬 코드를 C언어로 컴파일함으로 수행 속도를 향상 시킨다. 이런 특성으로 인해 C언어로 작성된 코드를 호출하는데 몇 가지 차별화되는 장점이 있다. 부가적인 과정을 필요로 하지 않기 때문에 C언어 코드를 부르는데 드는 노력이 적고, 부가적으로 작성하는 코드가 제거됨에 따라 호출 속도도 빨라진다. 부가적으로 작성하는 코드라 함은 파이썬에서 C언어의 코드를 부르기 위해 변수를 전달하고 결과를 전달 받는데 필요한 추가적인 코드를 의미한다. 사이썬은 자신의 함수를 부르듯이 C언어 함수의 호출이 가능하며, 파이썬 문법으로 작성한 함수도 바깥의 파이썬에 코드를 통한 호출이 필요 없을 경우 C언어와 같은 형태로 컴파일이 하여 고속 호출이 가능하다. 물론 외부 파이썬 코드가 부르는 함수의 경우는 파이썬 형태로 동작하도록 컴파일하는 기능이 들어있다.
도입 배경.
파이썬은 스크립트 언어이다. 스크립트 언어는 작성된 소스 코드를 실시간 해석하여 수행한다. 따라서 실시간 소스 코드 해석으로 인해 C언어와 같은 컴파일러 언어에 비해 수행 속도가 느리다. 컴파일러 언어는 소스 코드를 미리 번역해둔 실행 파일을 수행하기 때문에 실시간으로 코드를 해석할 필요가 없다. 따라서 스크립트 언어보다 수행 속도가 빠르다.
한편으로 파이썬은 많은 스크립트 언어들과 같이 동적 프로그래밍 언어 형태를 따르고 있다. 특히 변수의 타입을 동적으로 변형하고 있기 때문에 매번 변수의 타입을 확인해야 하는 부분이 들어 있다. 이 부분은 반복문과 사용될 경우 시간 손실을 더 많이 가져온다.
이렇게 파이썬이 가지고 있는 실시간 코드 해석 특성과 동적 변수 특성 등으로 인한 수행 속도의 한계를 극복하기 위해 출현한 프로그래밍 언어가 사이썬이다. 사이썬은 파이썬 스크립트를 C언어로 컴파일할 수 있다. 또한 형 타입도 정적으로 선언 할 수 있는 기능도 제공한다. 이런 동적 기능들로 인해 반복문이 많이 사용되는 경우 파이썬에 비해 수 십에서 수백 배까지 수행 속도를 증가 시킬 수 있다.
문법.
사이썬은 함수의 인자와 함수의 앞 단에서 변수의 타입을 선언할 수 있다. 저장 확장자는 .pyx를 사용한다. 따라서 컴파일을 한 이후에 파이썬에서 사용될 수 있다. 사용될 때는 파이썬 파일과 같이 import를 통해 사용할 수 있다.
사이썬 파일.
사이썬 코드는 함수의 인자와 내부 변수의 타입을 C언어와 유사한 형태로 선언한다.
사이썬 자체 코드.
아래 mathx.pyx에 있는 inverse() 함수의 예처럼 입력 인자의 변수를 float 타입으로 한정하게 된다. 다음으로 내부에 사용되는 변수인 y도 타입을 float로 한정시켰다. 이렇게 함으로 변수 관련된 연산시, 매번 변수의 타입을 확인하는 과정이 생략되어 속도 증가에 기여하게 된다.
def inverse(float x):
 cdef float y
 y = 1.0 / x
 return y
파이썬 라이브러리 호출.
아래 scix.pyx 사이썬 파일은 파이썬에서 제공하는 기본 라이브러리를 사용하고 있다. 이 경우에도 import math와 같이 라이브러를 불러들인뒤 해당 함수를 사용하면 동작한다. 사실 C언어로 번역된 뒤에 파이썬 함수를 부르는 방법이기 때문에 C언어에서 파이썬 함수를 부르는 기능을 내부적으로 가지고 있다. C-API를 이용한다면, 파이썬 함수 호출부분은 별도의 추가 코드를 작성해야 하는 번거로움이 있지만 사이썬은 자동으로 진행이 되도록 구성되어 있다.
import math
def kmc(float x):
 cdef float t
 t = math.log( x)
 return t
넘파이를 사용하는 사이썬.
넘파이(Numpy)는 수치 행렬을 효과적으로 사용하도록 하는 파이썬 라이브러리이다. 사이썬의 넘파이를 사용할 경우, 최적화할 수 있는 방법을 제공하고 있다. 넘파이를 사용하는 경우, 효과적인 C 언어로의 변환을 위해 사이썬 파일에는 다음과 같은 선언이 필요하다.
import numpy as np
cimport numpy as np
def cython_func(np.ndarray[np.uint32, ndim=1] H, np.ndarray[np.float64_t, ndim=2] X):
사이썬 컴파일.
사이썬으로 만들어진 파일들을 컴파일하기 위해서는 setup.py라는 파이썬 스크립트가 필요하다. 컴파일할 파일 이름에 대해 와일드카드(*)를 사용함으로써 동일한 디렉토리에 있는 모든 사이썬 파일들을 한꺼번에 컴파일하도록 동작한다.
from distutils.core import setup
from Cython.Build import cythonize
setup(name = 'Cython Library',
 ext_modules = cythonize("*.pyx"))
커맨드라인에서 setup.py를 이용하여 mathx.pyx를 컴파일하는 방법이다.
 python setup.py build_ext --inplace
사이썬 함수의 사용.
파이썬 커맨드라인에서 컴파일된 mathx.pyx와 scix.py 내부의 함수를 각각 실행하는 방법이다.
»&gt; import mathx, scix
»&gt; mathx.inverse(3.0)
»&gt; scix.kmc(5.0)
C/C++ 파일과 연결.
사이썬의 또하나의 장점은 C/C++ 짠 코드와 연결이 용이하다는 점이다.
C++ 파일과 연결.
C++ 파일을 연결한 경우, 사이썬 코드에 c++를 사용해서 컴파일 할 것을 요청해야 한다. 아래 link_cpp.pyx의 제일 상단에 있는 "# distutils: language = c++"이 그 것을 의미한다. 이 때 setup.py는 상단에서 정의한 파일을 변화없이 그대로 사용하면 된다. 
from MyCPP cimport mysort
def main():
 mysort()
위 파일을 테스트 하기 위한 간단한 파이썬 코드는 다음과 같다. 
import link_cpp
link_cpp.main()
결과는 다음과 같이 나오게 된다.
2 3 1
1 2 3
위의 link_cpp.pyx가 불러들이는 MyCPP.h와 MyCPP.cpp는 일반적인 C++ 코드로 작성하면 된다. 다만, 이 C++ 코드와 사이썬 코드를 연결하는 아래 MyCPP.pxd가 추가적으로 필요하다. 아래에서 제일 첫줄은 MyCPP.cpp을 사용한다는 의미이고 이를 적어주게 되면 자동으로 컴파일이 일어나기 때문에 setup.py에 추가로 C++ 컴파일할 코드를 적어주지 않아도 된다. 
cdef extern from "MyCPP.cpp":
 pass
cdef extern from "MyCPP.h" namespace "mycpp":
 void mysort()
C++과 연결하는 자세한 사용법은 사이썬 메뉴얼의 C++ 래핑 부분을 참조하면 된다.
사이썬의 장점.
사이썬은 C언어로된 함수를 파이썬 문법에서 부를 수 있도록 한다. C언어 함수를 부를 수 있는 다양한 방법들이 있지만 사이썬은 부가 코드를 작성하지 않고 바로 파이썬에서 호출이 가능한 장점이 있다. 예를 들어 일반화된 방법인 Python-C-API 방식을 이용하는 경우는 래핑 코드를 별도로 짜주어야 한다. 이런 과정을 자동화하는 SWIG이라는 방법이 있지만, SWIG를 통해 부가 코드를 생성하는 과정이 들어가는 불편함이 여전히 존재한다.
다음 예제는 앞선 scix.pyx에서 log()를 C 함수로 직접 호출하도록 변경한 코드이다. 부르기전에 선언하는 부분이 필요하다. 원래는 import math로 파이썬의 라이브러리를 부르는 형태이었지만, 이번에는 C언어의 라이브러리인 math.h를 부르도록 조정하였다. 이렇게 해주면 파이썬 문법을 사용하되 C언어 함수를 직접 호출하는 형태가 된다. 이 때 C언어에서 직접 부른 함수는 라이브러리 명칭을 생략하고 직접 적어준다. 즉, math.log()가 아닌 log()로 적는다.
cdef extern from "math.h":
 double log(double x)
def kmc(float x):
 cdef float t
 t = log(x)
 return t
앞선 sci.pyx와 새로운 scix_c.pyx를 호출하면 수행 시간이 차이가 난다. C언어의 함수를 직접 호출한 sci_c.pyx의 경우가 빠르다. 시간 이득은 파이썬과 달리 사이썬은 함수의 타입을 컴파일 시점에서 미리 정해둘 수 있기 때문에 이를 실행 시점에서 검사하는 시간을 생략할 수 있기 때문이다. 수행 시간은 ipython 환경에서 매직 함수인 timeit을 통해 측정이 되었다.
In [1]: %timeit scix.kmc(10.0)
1000000 loops, best of 3: 249 ns per loop
In [2]: %timeit scix_c.kmc(10.0)
10000000 loops, best of 3: 142 ns per loop