본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] C++ Extension 제작 시 pybind11 vs ctypes : 성능과 생산성을 잡는 2가지 결정적 방법과 차이점 분석

by Papa Martino V 2026. 4. 14.
728x90

pybind11 vs ctypes
pybind11 vs ctypes

 

파이썬(Python)은 데이터 과학, AI, 웹 개발 등 다양한 분야에서 최고의 생산성을 자랑하지만, 연산 집약적인 작업에서는 성능적 한계에 부딪히기 마련입니다. 이를 해결하기 위해 우리는 C++로 작성된 핵심 로직을 파이썬에서 불러와 사용하는 'C++ Extension' 기술을 활용합니다. 대표적인 도구로 pybind11ctypes가 꼽히지만, 프로젝트의 성격에 따라 선택의 기준은 명확히 달라져야 합니다. 본 포스팅에서는 실무 개발자의 관점에서 두 라이브러리의 3가지 핵심 차이점을 심도 있게 분석하고, 상황별로 무엇이 유리한지 결정할 수 있는 가이드를 제시합니다. 특히 복잡한 데이터 구조와 메모리 관리 측면에서 발생할 수 있는 잠재적 이슈를 사전에 방지하는 노하우를 담았습니다.


1. pybind11과 ctypes의 철학적 차이 및 기술적 배경

두 라이브러리는 파이썬과 C/C++를 연결한다는 목적은 같지만, 접근 방식은 정반대입니다. ctypes는 파이썬 표준 라이브러리에 포함된 외래 함수 인터페이스(FFI)로, 이미 빌드된 .so.dll 파일을 동적으로 로드하는 데 특화되어 있습니다. 반면, pybind11은 C++11 이상의 기능을 활용하여 C++ 코드 내에서 파이썬 객체를 직접 다루는 '바인딩' 환경을 구축합니다.

핵심 성능 및 기능 비교 요약

비교 항목 ctypes (Dynamic FFI) pybind11 (C++ Binding)
주요 장점 추가 컴파일 불필요, 표준 라이브러리 사용 C++ 객체지향 지원, STL 자동 변환
학습 곡선 낮음 (파이썬 코드 위주) 높음 (C++ 및 빌드 시스템 이해 필요)
데이터 변환 속도 상대적으로 느림 (Marshalling 비용) 매우 빠름 (Direct Binding)
복잡한 클래스 지원 어려움 (C-style Wrapper 필요) 완벽 지원 (상속, 다형성 포함)
빌드 의존성 없음 (Shared Library만 필요) CMake 또는 setuptools 설정 필수
유지보수성 타입 체크가 런타임에 발생하여 위험 컴파일 타임에 타입 체크 가능

2. 실무 적용을 위한 7가지 Sample Examples

개발자가 실무에서 즉시 활용할 수 있도록, 단순 계산부터 복잡한 행렬 연산 및 클래스 연동까지 단계별 예제를 구성했습니다.

Example 1: ctypes를 이용한 간단한 C 함수 호출 (수치 연산)

이미 컴파일된 공유 라이브러리가 있을 때 가장 빠르게 적용하는 방법입니다.

# [C++ - logic.cpp]
# extern "C" {
#     double add_values(double a, double b) { return a + b; }
# }
# 컴파일: g++ -shared -o liblogic.so -fPIC logic.cpp

import ctypes

lib = ctypes.CDLL('./liblogic.so')
lib.add_values.restype = ctypes.c_double
lib.add_values.argtypes = [ctypes.c_double, ctypes.c_double]

result = lib.add_values(10.5, 20.5)
print(f"Result: {result}")

Example 2: pybind11을 이용한 C++ STL (std::vector) 자동 변환

파이썬의 List를 C++의 vector로 변환하는 오버헤드를 최소화합니다.

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>
#include <numeric>

double sum_list(std::vector<double> input) {
    return std::accumulate(input.begin(), input.end(), 0.0);
}

PYBIND11_MODULE(fast_math, m) {
    m.def("sum_list", &sum_list, "A function that sums a list of doubles");
}

Example 3: ctypes에서 구조체(Struct) 데이터 주고받기

C 스타일의 구조체를 파이썬 인터페이스와 매핑하는 예제입니다.

import ctypes

class Point(ctypes.Structure):
    _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]

lib = ctypes.CDLL('./libgeo.so')
lib.move_point.argtypes = [ctypes.POINTER(Point), ctypes.c_int]

p = Point(10, 20)
lib.move_point(ctypes.byref(p), 5)
print(f"Moved Point: {p.x}, {p.y}")

Example 4: pybind11을 활용한 C++ 클래스 익스포트

객체지향 프로그래밍 구조를 파이썬으로 그대로 가져옵니다.

#include <pybind11/pybind11.h>

class Engine {
public:
    Engine(std::string name) : name(name) {}
    void start() { status = "Running"; }
    std::string get_status() { return status; }
private:
    std::string name;
    std::string status = "Stopped";
};

PYBIND11_MODULE(simulator, m) {
    pybind11::class_<Engine>(m, "Engine")
        .def(pybind11::init<const std::string &>())
        .def("start", &Engine::start)
        .def("get_status", &Engine::get_status);
}

Example 5: ctypes를 이용한 NumPy 배열 직접 참조 (Zero-copy)

대용량 데이터를 복사 없이 처리하여 성능을 극대화합니다.

import numpy as np
import ctypes

lib = ctypes.CDLL('./libprocess.so')
data = np.array([1, 2, 3, 4], dtype=np.int32)

# C++ 함수: void process_array(int* data, int size)
lib.process_array(data.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), len(data))

Example 6: pybind11에서 Python GIL(Global Interpreter Lock) 해제

멀티코어를 활용한 병렬 처리를 위해 C++ 영역에서 GIL을 해제하는 방법입니다.

#include <pybind11/pybind11.h>
#include <thread>

void heavy_computation() {
    pybind11::gil_scoped_release release; // GIL 해제
    // 시간이 오래 걸리는 연산 수행
    std::this_thread::sleep_for(std::chrono::seconds(2));
    pybind11::gil_scoped_acquire acquire; // GIL 다시 획득
}

Example 7: pybind11과 CMake를 이용한 패키징 설정

실제 배포를 위한 CMakeLists.txt의 표준 템플릿입니다.

cmake_minimum_required(VERSION 3.12)
project(MyExtension)

find_package(pybind11 REQUIRED)

pybind11_add_module(my_ext src/main.cpp)

# 추가적인 라이브러리 링크가 필요한 경우
# target_link_libraries(my_ext PRIVATE some_lib)

3. 최종 선택 가이드: 무엇이 더 유리한가?

결론적으로, ctypes는 외부 라이브러리(이미 빌드된 .so/.dll)를 가볍게 호출하거나, 추가적인 빌드 시스템 구축이 부담스러운 경우에 유리합니다. 반면, pybind11은 파이썬과 C++ 간의 복잡한 데이터 상호작용이 빈번하고, C++의 객체지향 기능을 파이썬에서 네이티브하게 사용해야 하는 대규모 프로젝트에서 압도적인 생산성을 보입니다. 성능적인 측면에서는 pybind11이 더 정교한 최적화가 가능하지만, 설정의 복잡함이 뒤따릅니다. 만약 여러분의 프로젝트가 NumPyPandas와 밀접하게 연동된다면, pybind11의 numpy.h 바인딩 기능을 적극 권장합니다.


4. 참고 문헌 및 출처

  • Pybind11 공식 문서 
  • Python 공식 문서 (ctypes) 
  • Effective C++ (Scott Meyers) - 메모리 관리 및 자원 관리 원칙
  • Real Python - Python Bindings: Calling C or C++ From Python
728x90