在 Python 中使用 ctypes 运行 C 动态链接库

ctypes 是 Python 的外部函数库,它提供了 C 语言兼容的数据类型,并允许调用动态链接库(如 Linux 下的 .so 文件)中的函数。大多数情况用于对接现有的 C/C++ 库、突破 GIL 的限制或者使用 libc.so.6 进行 System call。

例子1: 递归计算斐波那契数列

// fib_recursive.c
#include <stdio.h>

int fib_recursive(int n) {
    if (n <= 1) {
        return n;
    } else {
        return fib_recursive(n-1) + fib_recursive(n-2);
    }
}

一个简单的 C 语言递归计算斐波那契数列第 N 项的函数,使用 gcc 生成 so 共享库文件 。

$ gcc -shared -fPIC -o libfib.so fib_recursive.c

在 Python 代码中使用同样逻辑的递归函数,并用 time.perfcounter() 计算代码运行时间。

# fibonacci.py
import time
import ctypes

lib = ctypes.CDLL("./libfib.so")
# 声明 C 函数的参数类型和返回值类型
lib.fib_recursive.argtypes = [ctypes.c_int]
lib.fib_recursive.restype = ctypes.c_int

def fib_recursive_python(n):
    if n <= 1:
        return n
    return fib_recursive_python(n - 2) + fib_recursive_python(n - 1)

def fib_recursive_c(n):
    return lib.fib_recursive(n)

if __name__ == "__main__":

    start_time_python = time.perf_counter()
    result_python = fib_recursive_python(40) #通过 python 递归计算 fib 40
    end_time_python = time.perf_counter()

    start_time_c = time.perf_counter()
    result_c = fib_recursive_c(40)
    end_time_c = time.perf_counter()

    execution_time_python = end_time_python - start_time_python
    execution_time_c = end_time_c - start_time_c

    print(f"Python 代码运行耗时: {execution_time_python:.6f} 秒")
    print(f"C 加速代码运行耗时: {execution_time_c:.6f} 秒")
使用 C 加速 原生python
fib(35) 0.052312 秒 0.780042 秒
fib(40) 0.605450 秒 8.974071 秒

通过运行结果可以看出在计算斐波那契数列时 C 版本比 Python 版本快了大约 15 倍

例子2: 插入排序

因为插入排序是原地排序,C 实现时不依赖返回值传递数据(返回类型为 void),而是通过指针参数(int *arr)直接操作传入的内存地址。在排序完成后可以直接在 Py 代码中读取原数组获取排序后的结果。

/* 插入排序 */
void insertionSort(int *nums, int size) {
    // 外循环:已排序区间为 [0, i-1]
    for (int i = 1; i < size; i++) {
        int base = nums[i], j = i - 1;
        // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
        while (j >= 0 && nums[j] > base) {
            // 将 nums[j] 向右移动一位
            nums[j + 1] = nums[j];
            j--;
        }
        // 将 base 赋值到正确位置
        nums[j + 1] = base;
    }
}

同样使用 gcc 生成共享库文件。

$ gcc -shared -fPIC -o libinsertion_sort.so ./insertion_sort.c

Python 代码部分直接使用 list(range(n-1, -1, -1)) 生成完全倒序的数列来触发最坏时间复杂度 O(n²)

def insertion_sort(nums: list[int]):
    """插入排序"""
    # 外循环:已排序区间为 [0, i-1]
    for i in range(1, len(nums)):
        base = nums[i]
        j = i - 1
        # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
        while j >= 0 and nums[j] > base:
            nums[j + 1] = nums[j]  # 将 nums[j] 向右移动一位
            j -= 1
        nums[j + 1] = base  # 将 base 赋值到正确位置
import ctypes
import time

lib = ctypes.CDLL("./libinsertion_sort.so")
lib.insertionSort.argtypes = (
    ctypes.POINTER(ctypes.c_int),
    ctypes.c_int,
)
lib.insertionSort.restype = ctypes.POINTER(ctypes.c_int)


def insertionSort_c(arr):
    size = len(arr)
    arr_ptr = ctypes.cast(arr, ctypes.POINTER(ctypes.c_int))
    return lib.insertionSort(arr_ptr, size)

if __name__ == "__main__":
    arr = (ctypes.c_int * 20000)()

    # 填充数组,倒序的数列
    for j, val in zip(range(len(arr)), range(len(arr)-1, -1, -1)):
        arr[j] = val

    start_time = time.perf_counter()
    insertionSort_c(arr)
    end_time = time.perf_counter()

    print(f"用时:{(end_time - start_time):.6f}")
使用 C 加速 原生 Python
长度2000 0.004078 0.104218
长度20000 0.404812 10.453110

在整个过程中 C 基本稳定地比 Python 快约 25 倍