Python性能优化实战:Cython与ctypes核心原理与工程实践

Python性能优化实战:Cython与ctypes核心原理与工程实践 1. 项目概述为什么我们需要Python C扩展在Python社区里我们经常听到一个说法“Python慢”。这个“慢”通常指的是在纯Python环境下执行密集计算任务时比如数值计算、图像处理或者复杂算法模拟其执行效率远不及C、C这类编译型语言。我最早遇到这个问题是在处理一个物理引擎的模拟项目纯Python写的碰撞检测循环让整个仿真过程慢得像幻灯片。但Python的魅力在于其极高的开发效率和丰富的生态我们不可能为了性能就完全抛弃它。于是Python C扩展就成了连接“开发效率”和“执行性能”两座孤岛的关键桥梁。简单来说Python C扩展就是让我们能够用C或C编写关键的性能瓶颈模块然后将其编译成一个动态链接库在Windows上是.pyd在Linux/macOS上是.so最后在Python中像导入普通模块一样导入并使用它。这相当于给你的Python程序装上了一台高性能的“涡轮增压发动机”。实现这一目标的主流技术路径主要有两条Cython和ctypes。Cython更像是一个“超级编译器”它允许你写一种类似Python但能声明C类型的语言然后将其编译成高效的C扩展模块。而ctypes则是Python标准库的一部分它提供了一个轻量级的“外交官”让你能在Python中直接调用现有的、已编译好的C动态库函数无需额外编译步骤。这篇文章我将以一个从业超过十年的老码农视角带你从零开始深入Cython和ctypes的腹地。我们不仅会实现基础功能更会聚焦于那些真正影响性能的优化技巧、编译配置的“魔鬼细节”以及我在实际项目中踩过的无数个坑。无论你是想加速已有的Python项目还是希望将遗留的C/C代码库融入Python生态这里都有你需要的干货。2. 核心方案选型Cython vs ctypes何时用谁在动手之前我们必须先搞清楚Cython和ctypes各自的“脾气秉性”以及它们最适合的应用场景。选错了工具不仅事倍功半还可能引入一堆维护噩梦。2.1 Cython性能加速的“重型武器”Cython的核心思想是“静态类型声明”。在纯Python中一个变量a 10解释器在运行时才知道a是整数。而Cython允许你写cdef int a 10提前告诉编译器a是一个C整数从而省去了运行时类型检查和动态调度的开销。它本质上是一个编译器将.pyxCython源文件编译成.c文件再通过C编译器生成二进制扩展模块。Cython的典型适用场景计算密集型循环这是Cython的“主战场”。当你有一个嵌套很深的for循环里面全是数值运算时用Cython重写性能提升几十倍到上百倍是家常便饭。包装现有的C/C库尤其是C类Cython对C有不错的支持可以相对方便地包装C的类和模板比纯C接口友好得多。需要与Python对象深度交互Cython能非常自然地处理Python的列表、字典、对象等你可以在Cython代码里直接调用Python函数和操作Python数据结构虽然这会损失一部分性能但比用C API手动操作要安全、简单得多。它的优势在于性能极致通过静态类型和直接调用C函数能达到接近纯C的性能。开发体验相对友好语法是Python的超集学习曲线较平缓。特别是对于数学计算其语法与NumPy的配合非常优雅。生态成熟被SciPy、pandas、scikit-learn等众多知名科学计算库用作底层加速工具。2.2 ctypes轻量集成的“瑞士军刀”ctypes是Python内置的库。它不负责编译只负责“沟通”。你提供一个已经编译好的.dllWindows或.soLinux/macOS文件告诉ctypes里面函数的名称、参数类型和返回类型它就能帮你调用。ctypes的典型适用场景调用系统API或成熟的第三方闭源库比如调用Windows的user32.dll来操作窗口或者调用一个厂商提供的硬件驱动库。你不需要、也无法修改它们的源代码。快速原型验证你有一段现成的、稳定的C代码想快速在Python里试试效果用ctypes可以免去复杂的编译配置几分钟就能跑起来。轻量级、无依赖的集成你的项目不希望引入Cython这样的额外构建依赖只想用纯Python标准库解决问题。它的优势在于零编译依赖无需编译器只要你有动态库和头文件用于查看函数签名。纯Python所有代码都在Python脚本里部署简单跨平台行为相对一致主要处理动态库路径差异。入门极快对于简单的函数调用几行代码就能搞定。选型决策速查表特性维度Cythonctypes性能目标极致性能特别是循环和数值计算够用就好主要目标是功能集成而非极限优化开发语言Cython (类Python) C纯Python编译要求需要C编译器如gcc, MSVC和Cython工具链不需要直接调用已编译的二进制库适用对象需要重写或深度优化的Python代码需要包装C库现成的、稳定的C动态库系统API与Python交互深度、高效可直接操作Python对象较浅层主要通过类型转换传递数据学习成本中等需了解C类型和Cython特有语法低主要学习ctypes的几种数据类型维护成本中高需维护构建系统setup.py或pyproject.toml低逻辑都在Python脚本中我的经验之谈如果你的性能瓶颈明确且你愿意投入时间构建编译环境Cython是长期项目的首选。它带来的性能收益是战略性的。而ctypes则是战术性工具用于快速集成、调用外部库或者在那些“绝对不能有编译环节”的受限环境中使用。在实际项目中我经常两者混用用Cython编写核心算法模块用ctypes快速调用一个特定的系统库。3. Cython实战从零构建高性能扩展模块理论说再多不如亲手写一行。让我们从一个最经典的例子开始计算斐波那契数列。我们将对比纯Python、简单Cython和优化后Cython的性能差异并深入每一个编译和优化细节。3.1 基础环境搭建与项目结构首先确保你的系统有C编译器。Linux/macOS通常自带gcc/clang。Windows用户推荐安装Visual Studio Build Tools或MinGW。同时安装Cythonpip install cython我们创建一个清晰的项目目录fib_project/ ├── fib.py # 纯Python实现用于基准测试 ├── fib_cython.pyx # Cython源码 ├── setup.py # 构建脚本 └── test_fib.py # 测试脚本纯Python实现 (fib.py)def fib_py(n): if n 1: return n a, b 0, 1 for _ in range(2, n 1): a, b b, a b return b3.2 第一版Cython实现简单的.pyx文件创建fib_cython.pyx内容几乎和Python版一样# fib_cython.pyx def fib_cython_simple(n): if n 1: return n a, b 0, 1 for _ in range(2, n 1): a, b b, a b return b创建setup.py来构建它# setup.py from setuptools import setup from Cython.Build import cythonize setup( ext_modules cythonize(fib_cython.pyx), )在终端运行构建命令python setup.py build_ext --inplace--inplace参数会将编译好的扩展模块如fib_cython.cpython-39-x86_64-linux-gnu.so直接生成在当前目录方便导入。此时你已经在使用Cython了但这个版本几乎没有优化因为Cython仍然将其视为动态的Python对象和操作。我们来测试一下性能。创建test_fib.pyimport time from fib import fib_py # 导入编译好的Cython模块 import fib_cython n 100000 # 计算第10万个数值会非常大这里主要测循环速度 start time.time() result_py fib_py(n) py_time time.time() - start print(fPure Python: {py_time:.4f}s, result: {result_py}) start time.time() result_cy fib_cython.fib_cython_simple(n) cy_time time.time() - start print(fCython Simple: {cy_time:.4f}s, result: {result_cy}) print(fSpeedup: {py_time / cy_time:.2f}x)在我的测试机上这个简单Cython版本可能只有1.5倍到2倍的加速并不惊艳。因为循环变量a,b,_和循环体中的计算仍然被Cython解释为Python对象的操作。3.3 关键优化静态类型声明与直接C循环性能提升的钥匙在于cdef关键字。我们修改fib_cython.pyx创建一个优化版本# fib_cython.pyx def fib_cython_optimized(int n): # 声明局部变量为C类型 cdef long long a 0 cdef long long b 1 cdef long long temp cdef int i if n 1: return n # 使用C风格的for循环注意range变成了 i from 2 i n1 for i in range(2, n 1): temp a b a b b temp return b这里发生了质变函数参数n被声明为intCython知道它是C整数省去了Pythonint对象的构造和类型检查。局部变量使用cdef声明为C类型a,b,temp是C的long longi是C的int。它们在栈上分配操作是直接的CPU指令。循环是真正的C循环for i in range(...)在这里被编译成高效的C语言for循环而不是创建Python的range对象再迭代。重新编译并测试。性能提升通常是数十倍甚至上百倍。这才是Cython真正的威力所在。3.4 进阶优化使用cythonize指令与编译器优化Cython提供了编译指令directives和装饰器可以文件级或函数级进行微调。在fib_cython.pyx文件顶部添加# cython: language_level3 # cython: boundscheckFalse # cython: wraparoundFalse # cython: initializedcheckFalse # cython: cdivisionTrue或者在函数上使用装饰器import cython cython.boundscheck(False) cython.wraparound(False) cython.cdivision(True) def fib_cython_optimized(int n): ...这些指令的含义boundscheckFalse关闭索引越界检查。风险极高仅在你100%确定索引不会越界时使用。对于我们的循环变量i是安全的。wraparoundFalse关闭负索引回绕Python中list[-1]的行为。在操作数组时常用。initializedcheckFalse关闭对未初始化变量的检查轻微提升速度。cdivisionTrue使用C语言的整数除法规则向零取整而不是Python的向下取整。对于纯整数运算可以避免额外的检查。此外在setup.py中我们可以通过extra_compile_args和extra_link_args向C编译器传递优化标志from setuptools import setup, Extension from Cython.Build import cythonize import sys extra_compile_args [-O3, -marchnative] # 最高优化级别使用本地CPU指令集 if sys.platform win32: extra_compile_args [/O2] # Windows MSVC的优化标志 extensions [ Extension( fib_cython, [fib_cython.pyx], extra_compile_argsextra_compile_args, extra_link_argsextra_compile_args, ) ] setup( ext_modules cythonize(extensions, compiler_directives{language_level: 3}), )-O3和-marchnative能让GCC/Clang生成高度优化的机器码。但请注意-marchnative编译的二进制码可能无法在其他CPU型号的机器上运行分发时需要谨慎。3.5 与NumPy的无缝集成科学计算离不开NumPy。Cython对NumPy数组有原生支持能实现零拷贝zero-copy的视图操作性能极高。你需要安装NumPy并在.pyx文件中声明# fib_cython.pyx import numpy as np cimport numpy as cnp # 导入Cython级别的NumPy类型 # 必须声明NumPy数组的dtype这样Cython才能生成高效的代码 cnp.import_array() # 必须调用初始化NumPy C API def process_array(cnp.ndarray[cnp.double_t, ndim2] arr not None): cdef: Py_ssize_t i, j double value double[:, :] arr_view arr # 创建一个内存视图零拷贝 for i in range(arr_view.shape[0]): for j in range(arr_view.shape[1]): value arr_view[i, j] # 对value进行一些计算... arr_view[i, j] value * 2 # 直接修改原数组 return arr使用cnp.ndarray[type, ndim]进行类型声明并通过[:, :]创建内存视图memoryview是操作NumPy数组性能最优的方式。4. ctypes实战轻松调用现有C库现在我们把目光转向ctypes。假设我们有一个现成的C库libfastmath.soLinux或fastmath.dllWindows里面有一个计算向量点积的函数。C头文件 (fastmath.h) 可能长这样// fastmath.h #ifdef __cplusplus extern C { #endif double dot_product(const double* a, const double* b, int n); #ifdef __cplusplus } #endif对应的C实现 (fastmath.c):// fastmath.c double dot_product(const double* a, const double* b, int n) { double result 0.0; for (int i 0; i n; i) { result a[i] * b[i]; } return result; }我们先将其编译成动态库# Linux/macOS gcc -shared -fPIC -o libfastmath.so fastmath.c # Windows (MinGW) gcc -shared -o fastmath.dll fastmath.c4.1 基础调用加载库与声明函数在Python中使用ctypes调用它# test_ctypes.py import ctypes import os import sys import numpy as np # 1. 加载动态库 if sys.platform win32: lib ctypes.CDLL(./fastmath.dll) # Windows else: lib ctypes.CDLL(./libfastmath.so) # Linux/macOS # 2. 声明函数的参数类型和返回类型 lib.dot_product.argtypes [ ctypes.POINTER(ctypes.c_double), # const double* a ctypes.POINTER(ctypes.c_double), # const double* b ctypes.c_int # int n ] lib.dot_product.restype ctypes.c_double # 3. 准备数据并调用 size 1000000 arr_a np.random.randn(size).astype(np.float64) arr_b np.random.randn(size).astype(np.float64) # 关键获取NumPy数组的底层C指针 ptr_a arr_a.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) ptr_b arr_b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) result lib.dot_product(ptr_a, ptr_b, size) print(fctypes dot product result: {result}) # 验证一下 expected np.dot(arr_a, arr_b) print(fNumPy dot product result: {expected}) print(fDifference: {abs(result - expected)})核心步骤解析ctypes.CDLL()加载动态库。设置函数的argtypes和restype。这一步至关重要它告诉ctypes如何转换Python参数到C类型以及如何转换C返回值回Python类型。如果设置错误会导致段错误segmentation fault或错误结果。NumPy数组的.ctypes属性提供了到其底层数据缓冲区的桥梁data_as方法可以将其转换为特定类型的ctypes指针。这是实现零拷贝数据传递的关键避免了在Python和C之间复制大量数据。4.2 处理复杂数据结构与回调函数ctypes还能处理更复杂的场景比如结构体和回调函数。C端结构体// person.h typedef struct { char name[50]; int age; double height; } Person; void print_person(Person* p);Python端对应class Person(ctypes.Structure): _fields_ [ (name, ctypes.c_char * 50), (age, ctypes.c_int), (height, ctypes.c_double) ] lib.print_person.argtypes [ctypes.POINTER(Person)] lib.print_person.restype None p Person(bAlice, 30, 1.65) lib.print_person(ctypes.byref(p)) # 传递结构体指针回调函数C调用Python函数// callback.h typedef int (*CompareFunc)(int, int); int sort_with_callback(int* array, int n, CompareFunc cmp);# 定义回调函数类型 CMPFUNC ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) def py_compare(a, b): return a - b # 简单的比较 cmp_func CMPFUNC(py_compare) # 将Python函数包装成C回调函数 # 调用C函数传入回调 lib.sort_with_callback.argtypes [ctypes.POINTER(ctypes.c_int), ctypes.c_int, CMPFUNC] ...注意回调函数的使用要非常小心生命周期。确保C库不会在回调函数对象被Python垃圾回收后还尝试调用它。4.3 ctypes的性能陷阱与最佳实践类型匹配是头等大事argtypes和restype必须与C函数声明完全一致。一个int和long的错配在64位系统上就可能引发崩溃。指针与引用对于需要传递指针的参数使用ctypes.byref(x)生成轻量级指针或ctypes.pointer(x)创建新的指针对象。对于数组通常用x.ctypes.data_as(...)。内存管理如果C函数返回一个指针或者需要你分配内存传递给C函数你必须清楚谁负责释放。ctypes不会自动管理C端分配的内存。对于返回的字符串指针通常用ctypes.c_char_p接收然后通过ctypes.string_at()来获取内容但要注意原始指针是否后续会被C库释放。全局解释器锁GIL通过ctypes调用的C函数默认会持有GIL。如果你的C函数是CPU密集型的且不会调用Python API你可以释放GIL来允许其他Python线程运行。但这需要C函数本身是线程安全的并且你需要使用ctypes.CDLL的特定属性如lib.my_func.argtypes ...; lib.my_func.restype ...后再通过Python线程模块来操作比较复杂。通常对于纯计算函数用Cython来释放GIL更直接使用with nogil:块。平台差异动态库的命名.dllvs.sovs.dylib和加载路径需要处理。函数调用约定cdeclvsstdcall主要在Windows上也可能需要指定使用ctypes.WinDLL或设置lib.func_name.argtypes时会自动处理一部分。5. 高级优化与调试技巧无论是Cython还是ctypes到了追求极致性能或排查棘手Bug时都需要一些“高级装备”。5.1 Cython性能分析使用cython -aCython提供了一个极其有用的工具注解文件。在编译时加上-a标志或者运行cython -a your_module.pyx会生成一个同名的.html文件。cython -a fib_cython.pyx打开生成的fib_cython.html你会看到代码被高亮显示。黄色越深表示该行对应的C代码与Python交互越多性能开销越大。白色或浅黄色表示是纯C操作。这个可视化工具是优化Cython代码的“指南针”让你一眼就能找到性能热点。5.2 释放GIL让多线程真正并行Python的全局解释器锁GIL阻止了多线程同时执行Python字节码。但在C扩展中如果一段代码不操作Python对象我们可以释放GIL允许其他Python线程运行这对于计算密集型任务利用多核CPU至关重要。在Cython中使用nogil上下文管理器或声明函数为cpdef并指定nogilcdef double heavy_computation(double x) nogil: # 这个函数内部不能有任何Python对象操作如print修改list cdef double y x * x cdef int i for i in range(1000000): y y * 0.999 x return y def run_parallel(): cdef double result with nogil: result heavy_computation(3.14) # 在这个块内GIL被释放 return result在ctypes中释放GIL更复杂通常需要借助Python C API不推荐新手尝试。对于纯计算任务更常见的做法是用multiprocessing模块开多进程或者用Cython编写核心模块并释放GIL。5.3 内存视图Memoryviews的妙用前面在NumPy部分提到了内存视图它是Cython中处理连续内存缓冲区如NumPy数组、array模块、bytes的推荐方式。它比旧的buffer协议更强大、更安全。def sum_array(double[:] arr): # 一维双精度数组的内存视图 cdef double total 0 cdef Py_ssize_t i for i in range(arr.shape[0]): total arr[i] return total内存视图支持切片、跨步访问并且能自动处理数据的连续性C-contiguous或Fortran-contiguous生成最优的循环代码。5.4 调试Cython/C扩展调试二进制扩展模块比调试纯Python代码困难。以下是几种常用方法打印调试在Cython中使用print()会获取GIL或在C代码中使用printf。编译时确保不要过度优化如-O0。使用GDB/LLDB编译时加上-g调试符号标志在setup.py的extra_compile_args中添加-g。用gdb --args python your_script.py启动调试。在Cython生成的.c文件中设置断点例如b __pyx_pw_3foo_1some_function函数名会被修饰可以用gdb的info functions命令查找。Cython的cython.gdbCython提供了一个GDB插件可以让你在.pyx源文件级别进行调试而不是生成的.c文件。需要导入cython.gdb并按照文档设置。AddressSanitizer/Valgrind用于检测内存错误如越界、泄漏。在编译时链接相关库如-fsanitizeaddress运行程序工具会报告错误位置。6. 构建与分发让扩展模块“开箱即用”写好了扩展模块如何让它能被其他人方便地安装和使用你需要一个标准的打包方式。6.1 使用setuptools与pyproject.toml传统的setup.py仍然是可行的但现代Python打包更推荐使用pyproject.toml。以下是一个支持Cython的pyproject.toml示例# pyproject.toml [build-system] requires [setuptools61.0, cython0.29.30, numpy] # 构建依赖 build-backend setuptools.build_meta [project] name my_fast_module version 0.1.0 dependencies [numpy1.20] # 运行时依赖 [tool.setuptools] packages [my_fast_module] [tool.setuptools.package-dir] my_fast_module src [tool.setuptools.package-data] my_fast_module [*.pyx, *.pxd] # 确保包含Cython源文件 [tool.setuptools.cmdclass] build_ext Cython.Distutils.build_ext # 可选自定义构建命令对应的setup.py可以简化或者完全用pyproject.toml替代。使用pip install .即可进行构建和安装。6.2 处理平台依赖与编译标志不同平台Windows/Linux/macOS和不同Python版本ABI需要不同的编译设置。setuptools和Cython.Build.cythonize已经处理了大部分复杂性。但如果你需要更精细的控制可以自定义Extension对象# setup.py 或 setup.cfg 的扩展部分 import sys from setuptools import Extension extra_compile_args [] if sys.platform ! win32: extra_compile_args.extend([-O3, -marchnative, -fPIC]) else: extra_compile_args.extend([/O2, /fp:fast]) extensions [ Extension( my_fast_module.core, sources[src/my_fast_module/core.pyx], include_dirs[...], # 例如包含NumPy头文件: np.get_include() libraries[...], # 需要链接的系统库如 [m] 链接数学库 library_dirs[...], extra_compile_argsextra_compile_args, languagec, # 或 c ) ]6.3 针对不同Python版本和平台分发最省心的方式是发布轮子wheel特别是二进制轮子。对于纯Cython项目你可以发布源码分发sdist用户安装时会自动在其机器上编译。但对于依赖复杂C库的项目编译可能失败。发布二进制轮子需要为每个目标平台如manylinux、win_amd64、macosx进行构建这通常需要在CI/CD如GitHub Actions, Travis CI上完成。你可以使用cibuildwheel工具来简化这个过程。一个重要的实践是始终在setup.py或pyproject.toml中声明你的构建依赖如Cython,numpy和运行时依赖。对于NumPy有一个特殊模式在setup.py中import numpy并设置include_dirs[np.get_include()]同时通过setup_requires参数已不推荐或在pyproject.toml的build-system.requires中声明numpy以确保构建时NumPy可用。7. 常见问题与排查实录在这一部分我分享一些我亲身踩过、并且看到无数同行也掉进去的坑。希望你能绕道而行。7.1 段错误Segmentation Fault这是最令人头疼的错误意味着你的程序访问了不该访问的内存。Cython/ctypes中类型声明错误这是最常见的原因。比如C函数期望一个int*你传递了一个Python整数而不是指针。在Cython中确保cdef声明的类型与C函数签名严格匹配。在ctypes中反复检查argtypes。数组越界在Cython中关闭了boundscheck但循环索引写错了。建议在开发阶段保持boundscheckTrue优化阶段再关闭。使用已释放的内存在ctypes中如果你从一个C函数接收了一个指针并假设它指向的内存一直有效但C函数内部可能已经释放了它。务必查阅C库的文档明确内存所有权。调试方法使用GDB在崩溃处查看回溯bt。在Cython生成的.c文件中找到对应行号结合cython -a生成的html。7.2 导入错误ImportError无法导入编译好的模块。模块名不匹配setup.py中Extension的第一个参数是模块名它必须和你在Python中import的名字一致。如果模块在子包中名字应该是package.submodule。依赖的共享库未找到你的扩展模块链接了其他库如libm.so。在Linux上使用ldd your_module.so检查依赖。可能需要设置LD_LIBRARY_PATH或将库安装到系统路径。ABI不兼容用Python 3.8编译的扩展模块无法在Python 3.9下导入。确保用目标Python版本进行编译。使用虚拟环境venv可以很好地隔离环境。7.3 性能未达预期你觉得已经用了Cython但速度提升不明显。没有使用静态类型检查cython -a生成的html看看热点循环是否还是深黄色。确保在循环内部的所有变量都用cdef声明了C类型。仍在频繁调用Python函数/操作Python对象在性能关键的循环中避免调用len()、append()、[]对Python列表等Python操作。如果必须使用考虑将数据转换为C数组或内存视图后再处理。没有利用编译器优化检查setup.py中的extra_compile_args是否添加了-O2或-O3。算法本身是瓶颈Cython只能优化代码的实现开销无法改变算法的时间复杂度。一个O(n²)的算法即使用C重写对于大数据集依然慢。首先优化算法。7.4 Windows下的特殊问题编译器选择Windows上官方CPython是用MSVC编译的所以最好也使用MSVC如Visual Studio 2019/2022的Build Tools来编译扩展。MinGW有时会有兼容性问题。安装py -m pip install setuptools通常会自动配置。链接错误LNK2001, LNK2019通常是缺少库文件。在Extension的libraries参数中添加正确的库名。对于Windows系统库名字可能不同如kernel32。路径与编码Windows路径使用反斜杠和可能的中文用户名会导致问题。在构建脚本中尽量使用pathlib.Path处理路径并注意字符串编码使用字节字符串b...处理文件路径可能更安全。7.5 Cython与ctypes的混合使用有时你会遇到这种情况核心算法用Cython写但需要调用一个只有二进制动态库的第三方库。这时可以混合使用。方案在Cython中调用C函数。你可以在Cython中直接声明C函数原型然后链接对应的库。# 在 .pyx 或 .pxd 文件中 cdef extern from some_lib.h: double external_compute(double x, int n) def my_cython_func(double x, int n): cdef double result # ... 一些Cython计算 ... result external_compute(x, n) # 直接调用C函数 # ... 更多计算 ... return result然后在setup.py的Extension中通过libraries[some_lib]和library_dirs[/path/to/lib]来链接这个库。这样Cython模块在编译时就会链接到libsome_lib.so你就能在Cython代码里直接使用那些C函数了。这比通过ctypes在Python层调用效率更高因为省去了ctypes的调用开销。