ABOUT ME

-

Total
-
  • Python 모듈 C언어로 만들기
    컴퓨터/파이썬 2020. 10. 22. 00:34
    728x90
    반응형

    Python 모듈

     

    1. 소개

    ctype으로 C언어 코딩 실행하기와 비슷한데

    실제로 pip으로 설치할 수 있는 파이썬 패키지를 C언어로 만들어 볼 것이다.

     

    Python Github의 C 파일들은 보면 문법이 어느 정도 감이 잡힐 것이다.

     

    할 것

    팩토리얼(N!) 결과를 구해내는 방법을

    C언어 모듈, Cython (.pyx) 모듈, 기본 파이썬 함수

    위 3가지를 비교할 것이다.

     

    C언어 파이썬 모듈 제작

     

    1. 우선 setup.py을 대충 아래처럼 만든다.

    try:
        from setuptools import setup, Extension
    except ImportError:
        from distutils.core import setup, Extension
    
    setup(
        name="C Factorial",
        version="0.1",
        description="C",
        author="",
        author_email="",
        license="MIT",
        ext_modules=[Extension("pycmath", ["src/factorial_mod.c"])],
        python_requires=">=2.8",
        include_package_data=True,
        classifiers=[
            "Programming Language :: Python :: 3",
            "License :: OSI Approved :: MIT License",
            "Operating System :: OS Independent",
        ],
        zip_safe=False,
    )
    

     

    2. C 코드 작성

    Python.h 파일은 파이썬/include 폴더나, anaconda는 아나콘다 설치 폴더\include 폴더에 있다.

    그리고, vscode를 이용 중이라면 includePath에 위 폴더를 추가하면 된다.

    {
      "configurations": [
        {
          "name": "Win32",
          "includePath": ["${workspaceFolder}/src", "${workspaceFolder}/include"],
          "defines": ["_DEBUG", "UNICODE", "_UNICODE"],
           ~~
        }
      ],
    }
    

     

    3. 팩토리얼 함수

    매우 큰 크기의 수를 return 해야 하므로 long long을 타입으로 지정했다.

    static long long c_fact(long N);
    
    static long long c_fact(long N)
    {
      long long sum = 1;
      int i;
      for (i = 1; i <= N; i++)
      {
        sum *= i;
      }
    
      return sum;
    }
    

     

    4. 인터페이스

    C 코드를 사용하게 해주는 파이썬 인터페이스를 아래처럼 만든다.

    (Python으로 C를 작성할 때 거의 모든 것을 PyObject *Name으로 작성하면 된다.)

    static PyObject *python_factorial(PyObject *module, PyObject *arg)
    {
      PyObject *ret = NULL;  // 결과
      assert(arg);
      Py_INCREF(arg);
      if (!PyLong_CheckExact(arg))
      {
        PyErr_SetString(PyExc_ValueError, "Argument is not an integer.");
        goto except;
      }
      long ordinal = PyLong_AsLong(arg);
      long long result = c_fact(ordinal);  // 팩토리얼 계산
      ret = PyLong_FromLongLong(result);  // 파이썬 long long
      assert(!PyErr_Occurred());
      assert(ret);
      goto finally;
    except:
      Py_XDECREF(ret);
      ret = NULL;
    finally:
      Py_DECREF(arg);
      return ret;
    }
    

     

    5. 파이썬 모듈 제작 함수

    METH_O는 PyArg_ParseTuple()을 부르는 대신 PyObject* 타입으로 지정

    PyMODINIT_FUNC PyInit_모듈이름(void), 모듈 이름으로 안 하면 오류가 나는 것 같다.

    static PyMethodDef pycmathExt_methods[] = {
        {"factorial", python_factorial, METH_O, "Factorial of N."},
        {NULL, NULL, 0, NULL}
        /* {함수 이름, 파이썬 인터페이스, 매개변수 타입, "설명"} */
    };
    
    #if PY_MAJOR_VERSION >= 3
    PyDoc_STRVAR(module_doc, "Factorial in C.");
    
    static struct PyModuleDef pycmathExt = {
        PyModuleDef_HEAD_INIT,
        "pycmath",  // 모듈 이름
        module_doc,
        -1,
        pycmathExt_methods,
        NULL,
        NULL,
        NULL,
        NULL};
    
    PyMODINIT_FUNC PyInit_pycmath(void)
    {
      return PyModule_Create(&pycmathExt);
    }
    #endif
    

     

    더보기
    #include &lt;Python.h>
    
    static long long c_fact(long N);
    
    static long long c_fact(long N)
    {
      long long sum = 1;
      int i;
      for (i = 1; i <= N; i++)
      {
        sum *= i;
      }
    
      return sum;
    }
    
    static PyObject *python_factorial(PyObject *module, PyObject *arg)
    {
      PyObject *ret = NULL;
      assert(arg);
      Py_INCREF(arg);
      if (!PyLong_CheckExact(arg))
      {
        PyErr_SetString(PyExc_ValueError, "Argument is not an integer.");
        goto except;
      }
      long ordinal = PyLong_AsLong(arg);
      long long result = c_fact(ordinal);
      ret = PyLong_FromLongLong(result);
      assert(!PyErr_Occurred());
      assert(ret);
      goto finally;
    except:
      Py_XDECREF(ret);
      ret = NULL;
    finally:
      Py_DECREF(arg);
      return ret;
    }
    
    static PyMethodDef pycmathExt_methods[] = {
        {"factorial", python_factorial, METH_O, "Factorial of N."},
        {NULL, NULL, 0, NULL} /* sentinel */
    };
    
    #if PY_MAJOR_VERSION >= 3
    
    PyDoc_STRVAR(module_doc, "Factorial in C.");
    
    static struct PyModuleDef pycmathExt = {
        PyModuleDef_HEAD_INIT,
        "pycmath",
        module_doc,
        -1,
        pycmathExt_methods,
        NULL,
        NULL,
        NULL,
        NULL};
    
    PyMODINIT_FUNC PyInit_pycmath(void)
    {
      return PyModule_Create(&pycmathExt);
    }
    
    #endif

     

    빌드 및 설치

    python setup.py build_ext --inplace
    
    python setup.py install
    

     

    사용법

    import pycmath
    
    pycmath.factorial(100003)
    

     

    Cython으로 모듈 제작

    factorial.pyx 형식으로 만든 후, cdef나 cpdef 이용

     

    cdef: c언어에서 불릴 함수, 무조건 다른 함수에서 실행되는 방법이여야 함.

    def, cpdef: 파이썬에서 불릴 함수 (ex. fact_cpdef(1000)으로 바로 파이썬에서 이용)

     

    1. cdef 이용

    def factorial(long n):
        return fact_in_c(n)
    
    cdef long long fact_in_c(long n):
        sum = 1
        
        for i in range(1, n + 1):
            sum *= i
            
        return sum

     

    2. cpdef 이용

    cpdef long long fact_cpdef(long n):
        sum = 1
        
        for i in range(1, n + 1):
            sum *= i
            
        return sum

     

    3. setup.py

    cythonize을 이용하면 된다.

    try:
        from setuptools import setup
    except ImportError:
        from distutils.core import setup
        
    from Cython.Build import cythonize
    
    setup(
        name="C Factorial",
        version="0.1",
        description="C",
        author="",
        author_email="",
        license="MIT",
        ext_modules=cythonize("src/factorial.pyx", language_level="3"),
        python_requires=">=2.8",
        include_package_data=True,
        classifiers=[
            "Programming Language :: Python :: 3",
            "License :: OSI Approved :: MIT License",
            "Operating System :: OS Independent",
        ],
        zip_safe=False,
    )
    

     

    결과

    memory-profiler를 설치해서 메모리 사용량(메가바이트) 까지 확인해보았다.

    pip install memory-profiler
    from factorial import factorial, factorial_cdef
    from timeit import default_timer, timeit
    from memory_profiler import memory_usage
    import pycmath
    
    
    # 파이썬 순수 코드
    def pyfactorial(n):
        sum = 1
        for i in range(1, n + 1):
            sum *= i
        return sum
    
    
    mem_usage1 = memory_usage()
    start = default_timer()
    pycmath.factorial(100003)  # C언어 버전 실행
    print(f"C: {default_timer() - start:.5f}초")
    mem_usage2 = memory_usage()[0] - mem_usage1[0]
    print(mem_usage2, "Mb")
    
    mem_usage1 = memory_usage()
    start = default_timer()
    factorial(100003)  # Cython cpdef 이용
    print(f"Cython: {default_timer() - start:.5f}초")
    mem_usage2 = memory_usage()[0] - mem_usage1[0]
    print(mem_usage2, "Mb")
    
    mem_usage1 = memory_usage()
    start = default_timer()
    factorial_cdef(100003)  # Cython cdef 이용
    print(f"Cython: {default_timer() - start:.5f}초")
    mem_usage2 = memory_usage()[0] - mem_usage1[0]
    print(mem_usage2, "Mb")
    
    mem_usage1 = memory_usage()
    start = default_timer()
    pyfactorial(100003)  # Python 함수 이용
    print(f"Python: {default_timer() - start:.5f}초")
    mem_usage2 = memory_usage()[0] - mem_usage1[0]
    print(mem_usage2, "Mb")
    
    # 결과
    C: 0.00003초
    0.0078125 Mb
    
    Cython (cpdef): 0.00450초
    0.0078125 Mb
    
    Cython (cdef): 0.00278초
    0.0 Mb
    
    Python: 0.00593초
    0.0 Mb

    C > cdef > cpdef > Python

     

    결론 : C언어로 작성하고 python에 이용하자.

     

    외전

    내 모듈을 pypi에 업로드 하고 싶으면

    pypi 사이트 로그인 후, pypi.org/

     

    PyPI · The Python Package Index

    The Python Package Index (PyPI) is a repository of software for the Python programming language.

    pypi.org

    아래 명령어 입력

    python setup.py sdist bdist_wheel
    
    twine upload --skip-existing dist/*

     

    참고

    파이썬 C모듈 제작 기본 문법 Github

     

    starnight/python-c-extension

    This is the practice of Python C extensions. Contribute to starnight/python-c-extension development by creating an account on GitHub.

    github.com

    파이썬 공식 C API 설명

     

    공통 객체 구조체 — 파이썬 설명서 주석판

    공통 객체 구조체 파이썬의 객체 형 정의에 사용되는 많은 구조체가 있습니다. 이 섹션에서는 이러한 구조체와 사용 방법에 관해 설명합니다. 기본 객체 형과 매크로 모든 파이썬 객체는 궁극적

    python.flowdas.com

     

    728x90

    '컴퓨터 > 파이썬' 카테고리의 다른 글

    Cython을 이용한 Bubble Sort  (0) 2020.10.24
    Python에서 C/C++언어 함수 실행하기  (0) 2020.10.21
    Python kth Hamming number (해밍 수)  (0) 2020.10.19

    댓글