코드를 빠르게 하는 가장 쉬운 방법은 처리해야할 작업의 양, 데이터를 간소화하고 프로파일링을 통해 병목이 발생하는 구문을 최적화하는 것입니다. 특히, C 언어를 이용한 더 낮은 수준 (low level)으로 코드를 기계어로 컴파일하면 간단하게 큰 속도 향상을 얻을 수 있습니다. 특히, 같은 연산을 무수히 반복하는 루프를 포함하는 수학적인 코드에서는 임시 객체를 사용할 확률이 높기에 컴파일을 통해 큰 이득을 얻을 수 있습니다. 다만, 정규 표현식, 문자열 연산, 데이터베이스 등의 외부 라이브러리를 호출하는 코드는 속도 개선을 기대하기 어렵고 numpy 또한 벡터 연산을 집중해서 호출하고 임시 객체를 많이 생성하지 않아 컴파일이 별로 도움이 되지는 않습니다. (이미 C기반으로 최적화가 되어있기도 하고요)
LLVM 기반의 컴파일을 제공하는 Numba 라는 것도 있지만 이번 포스트에서는 순수 C기반의 컴파일을 수행하는 사이썬을 이용하여 쥴리아 집합 계산 속도를 향상시켜 보겠습니다.
시작에 앞서
JIT vs AOT
파이썬 컴파일 도구는 크게 1) 미리 컴파일하는 Ahead Of Time (AOT) 컴파일 방식과 2) 적절한 때에 컴파일하는 Just In Time (JIT) 컴파일 방식으로 나눌 수 있습니다. AOT 방식에는 사이썬이 해당하고 JIT 방식에는 Numba, Pypy 가 해당합니다.
AOT 방식은 사용할 컴퓨터에 특화된 정적 라이브러리를 생성합니다. numpy, scipy, sckit-learn 등 패키지를 내려받으면 사이썬을 이용해서 라이브러리의 일부를 설치하는 컴퓨터에 맞게 컴파일하며, 프로그램 실행 전에 미리 컴파일하므로 코드에서 해당 라이브러리를 사용할 수 있습니다.
JIT 방식은 어떤 작업도 미리 하지 않고 컴파일러가 적절한 때에 컴파일을 시작하는 콜드 스타트 방식입니다. 따라서 프로그램 실행 후에야 처음 컴파일하므로 프로그램이 초반에는 느리게 실행됩니다만 여러 번 실행하는 스크립트에서라면 괜찮겠죠. 따라서 일반적으로 AOT 컴파일 방식이 최선이지만 C언어 사용, 빌드 등의 직접적인 노력이 많이 드는 편입니다.
환경 설정
먼저 Cython 패키지를 설치합니다. 사이썬은 타입을 명시한 파이썬 코드를 컴파일된 확장 모듈로 변경해주는 컴파일러로 빌드해서 얻은 확장 모듈은 import 문을 사용해서 일반적인 파이선 모듈처럼 사용할 수 있습니다.
pip install Cython
이후 Cython 패키지를 이용해 파이썬 코드를 빌드하는데, 이때 microsoft visual studio 빌드 툴이 필요합니다. 이 포스트대로 따라하면 됩니다.
줄리아 집합 다시보기
사이썬을 이용한 순수 파이썬 코드 컴파일
컴파일된 확장 모듈을 작성하는 과정에는 다음과 같은 세 가지 파일이 필요합니다.
- 호출하려는 파이선 코드 (여기서는 줄리아 집합 코드)
- 새로 컴파일하려는 .pyx 파일
- 확장 모듈을 작성하기 위해 사이썬을 호출하는 과정이 있는 setup.py 파일
이 방법을 사용하면 setup.py 파일에서 사이썬을 사용해서 .pyx 파일을 컴파일하게 되며 컴파일된 모듈은 유닉스 계열 시스템에서는 .so 파일, 윈도우에서는 .pyd 파일이 생성됩니다. 먼저 컴파일할 대상인 cythonfn.pyx 파일을 다음과 같이 작성합니다. (기존 코드에서 calculate_z 함수 부분만 별도의 파일로 분리합니다)
# cythonfn.pyx
def calculate_z(maxiter, zs, cs):
output = [0] * len(zs)
for i in range(len(zs)):
n = 0
z = zs[i]
c = cs[i]
while abs(z) < 2 and n < maxiter:
z = z*z + c
n += 1
output[i] = n
return output
다음으로는 다음과 같이 setup.py 파일을 작성하고 build_ext 옵션을 주고 실행합니다. build_ext 옵션을 주면 사이썬은 cythonfn.pyx 파일을 찾아 윈도우 기준으로 cythonfn.cp39-win_amd64.pyd 파일로 빌드하게 되며 --inplace 옵션까지 주게 되면 컴파일된 모듈을 분리된 별도 디렉터리가 아니라 현재 폴더에 생성하게 됩니다. 빌드가 끝나면 calculate.c 파일도 생성됩니다.
# setup.py
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("cythonfn.pyx",
compiler_directives={"language_level": "3"}))
- "language_level" 은 3으로 하드코딩해서 파이썬 3.x 버전 지원을 명시합니다.
(venv) C:\Users\yukua\Desktop\BLOG\julia>python setup.py build_ext --inplace
running build_ext
creating build
creating build\temp.win-amd64-3.9
creating build\temp.win-amd64-3.9\Release
"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.33.31629\bin\HostX86\x64\cl.exe" /c /nologo /O2 /W3 /GL /DNDEBUG /MD -IC:\Users\yukua\Desktop\BLOG\venv\include -IC:\Users\yukua\AppData\Local\Programs
\Python\Python39\include -IC:\Users\yukua\AppData\Local\Programs\Python\Python39\Include "-IC:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.33.31629\include" "-IC:\Program Files (x86)\Microsoft Visual
Studio\2022\BuildTools\VC\Auxiliary\VS\include" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\\include\10.0.19041.0\\um" "-IC:\Program Files (x86)\Windows Kits\10\\in
clude\10.0.19041.0\\shared" "-IC:\Program Files (x86)\Windows Kits\10\\include\10.0.19041.0\\winrt" "-IC:\Program Files (x86)\Windows Kits\10\\include\10.0.19041.0\\cppwinrt" /Tccythonfn.c /Fobuild\temp.win-amd64-3.9\Release\cythonf
n.obj
cythonfn.c
creating C:\Users\yukua\Desktop\BLOG\julia\build\lib.win-amd64-3.9
"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.33.31629\bin\HostX86\x64\link.exe" /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\Users\yukua\Desktop\BLOG\venv\li
bs /LIBPATH:C:\Users\yukua\AppData\Local\Programs\Python\Python39\libs /LIBPATH:C:\Users\yukua\AppData\Local\Programs\Python\Python39 /LIBPATH:C:\Users\yukua\Desktop\BLOG\venv\PCbuild\amd64 "/LIBPATH:C:\Program Files (x86)\Microsoft
Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.33.31629\lib\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\ucrt\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\\lib\10.0.19041.0\\um\x64" /EXPORT:PyInit
_cythonfn build\temp.win-amd64-3.9\Release\cythonfn.obj /OUT:build\lib.win-amd64-3.9\cythonfn.cp39-win_amd64.pyd /IMPLIB:build\temp.win-amd64-3.9\Release\cythonfn.cp39-win_amd64.lib
build\temp.win-amd64-3.9\Release\cythonfn.cp39-win_amd64.lib 라이브러리 및 build\temp.win-amd64-3.9\Release\cythonfn.cp39-win_amd64.exp 개체를 생성하고 있습니다.
코드를 생성하고 있습니다.
코드를 생성했습니다.
- Cython 설치가 잘 되었다면 위처럼 잘 build 되어야 하고 다음과 같은 파일들이 생성됩니다.
속도 비교
먼저 사이썬 컴파일하지 않은 상태에서 이 포스트의 코드를 실행하면, 1000x1000 크기와 maxiter=300 조건에서 대략 5초 정도가 소요됩니다. 기존 calculate_z 함수를 빌드한 확장 모듈 cythonfn 의 함수로 대체하면 대략 3.2초로 30% 정도 빨라지네요.
...
print(f"Total elements: {len(zs)}")
start_time = time.time()
output = cythonfn.calculate_z(maxiter, zs, cs)
# output = calculate_z(maxiter, zs, cs)
end_time = time.time()
elapsed = end_time - start_time
print(calculate_z.__name__, f" took {elapsed} seconds")
...
'Computer > Python' 카테고리의 다른 글
파이썬 코드 C언어로 컴파일하기 (3) - 사이썬과 넘파이 (0) | 2022.10.01 |
---|---|
파이썬 코드 C언어로 컴파일하기 (2) (0) | 2022.10.01 |
행렬과 벡터 연산 (6) - numexpr 모듈 이용하기 (2) | 2022.09.25 |
행렬과 벡터 연산 (5) - numpy 배열 메모리 최적화 (2) | 2022.09.24 |
행렬과 벡터 연산 (4) - numpy 배열을 이용한 확산 방정식 (0) | 2022.09.24 |