在 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 倍。