Python Development With Boost.Python

Python 을 사용하다보면, 그 편리함에 감탄하다가도, 좀 더 나은 성능을 가지고 싶다라는 욕망에 몸을 던지게 됩니다. 결국 가끔씩 속도의 유혹에 이기지 못해, Binding 이라는 외도를 하게 되는데……

이 외도를 하는 방법에 3가지가 있다고 합니다.
1. Pure Python Binding
2. Boost.Python
3. SWIG

여기서 제가 해본 것은 1,2번입니다. 1,2번은 실제로 성능 차이는 거의 없는 것 같습니다.

-----Boost.Python Binding----
4 function calls in 1.232 seconds
Ordered by: standard name

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 1.232 1.232 :1()
1 1.190 1.190 1.232 1.232 test.py:4(testgo)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.042 0.042 0.042 0.042 {range}

----Pure Python Binding ----
1000004 function calls in 1.266 seconds

Ordered by: standard name

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 1.266 1.266 :1()
1 0.509 0.509 1.266 1.266 test.py:4(testgo)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1000000 0.715 0.000 0.715 0.000 {pure_test.pure_test}
1 0.042 0.042 0.042 0.042 {range}

단지 이상한 것은 Pure Python Binding 에서는 cProfile이 제대로된 호출을 찾아내는데, Boost.Python 에서는 제대로 된 Profile 결과가 나오지 않습니다. 다만, 사용시간은 제대로 나옵니다.

그런데, Pure Python Binding 은 개발이 조금 귀찮습니다. Boost.Python 은 Boost를 설치해야 합니다. 설치시에도 –with-python 옵션을 줘야만 함께 생성됩니다. 그 대신 제공해주는 것이 풍부합니다. 그래서 간단한 Boost.Python 모듈 소스를 소개합니다.

먼저 개발을 위해서는 다음과 같은 setup.py를 만들어줘야 합니다.

from distutils.core import setup, Extension

module1 = Extension('boost_test',
                     include_dirs = ['/boost/include'],
                     libraries = ['boost_python'],
                     library_dirs = ['/boost/lib'],
                     sources = ['src/boost_test.cpp'])

setup (name = 'boost_test',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

그리고 소스는 굉장히 간단합니다. 실용성을 위해서 list 안에 list 가 있는 케이스로
작성을 했습니다. list 나 tuple 이나 사용법은 동일합니다.

#include 
#include 
#include 

using namespace boost::python;

int get_int( tuple list1, list listoflist ){
    return boost::python::len(list1);
}

const char *get_string( list list1, list listoflist ){
    list mylist = boost::python::extract(listoflist[1]);
    //str 을 쓰면 object 를 string으로 만들어 줍니다.
    //str 을 안쓰고 다른 type 이 오면 TypeConvertError 가 생깁니다.
    std::string k = boost::python::extract(str(mylist[0]));
    return k.c_str();
}

BOOST_PYTHON_MODULE(boost_test)
{
    def("get_string", &get_string );
    def("get_int", &get_int );
}

마지막으로 간단한 테스트 코드입니다.

import boost_test

if __name__=="__main__":
    print boost_test.get_int( ('1','2','3'), ['2'] )
    print boost_test.get_string( ['1','2','3'], [['2'],['1','3']] )

class 를 binding 하는 방법 역시 boost.python 에서는 굉장히 쉽고, 이미 boost 페이지 내에서 찾기 쉽기 때문에 생략합니다.