Skip to content

函数与模块级优化

本章讲解函数级优化技术。函数是代码组织的基本单元,优化函数签名和调用方式是获得Cython高性能的关键。

学习路径:签名优化 → 内联函数 → Python调用开销 → 模块级状态

核心原则:

  • 参数和返回值使用C类型获得最大性能
  • 简单函数用inline消除调用开销
  • 减少Python函数调用,避免GIL开销

功能说明:为函数参数添加类型声明,消除Python对象封装开销。

# 无类型参数 - Python对象开销
def sum_py(values):
total = 0
for v in values:
total += v # v是Python对象,+操作有类型检查
return total
# 有类型参数 - C级开销
cpdef double sum_typed(list values):
cdef double total = 0.0
cdef int i
for i in range(len(values)):
total += values[i] # values[i]是C double
return total

性能对比(1000万元素):

函数耗时加速比
sum_py0.89s1x
sum_typed0.12s7.4x

功能说明:声明返回值类型避免Python对象创建。

# 返回Python int - 有GC开销
def return_int_py():
return 42
# 返回C int - 无GC开销
cdef int return_int_c():
return 42
# 返回double - 高精度
cdef double return_double():
return 3.141592653589793

输出示例:

>>> return_int_py()
42 # Python int对象
>>> return_int_c()
42 # C int(Python调用时自动转换)
>>> return_double()
3.141592653589793

最佳实践:cpdef函数返回值会在Python调用时转换,选择合适类型避免不必要开销。

功能说明:为可选参数提供默认值,简化调用。

# Python风格默认参数
cpdef int process(int n, int multiplier=1, int offset=0):
return n * multiplier + offset
# 使用示例
result = process(10) # multiplier=1, offset=0 → 10
result = process(10, 2) # multiplier=2, offset=0 → 20
result = process(10, 2, 5) # multiplier=2, offset=5 → 25

输出示例:

>>> process(10)
10
>>> process(10, 2)
20
>>> process(10, 2, 5)
25

限制:默认参数必须是Python对象或编译时可求值的常量。


功能说明:内联函数将调用点替换为函数体,消除调用开销。

# 内联函数 - 调用开销接近零
cdef inline int square(int x) nogil:
return x * x
# 使用内联函数
cdef int sum_squares(int n):
cdef int total = 0
cdef int i
for i in range(n):
total += square(i) # 展开为 i * i,无函数调用
return total

输出示例:

>>> sum_squares(5)
30 # 0²+1²+2²+3²+4² = 30

功能说明:简单函数适合内联,复杂函数内联会增大代码体积。

# 简单函数适合内联
cdef inline double celsius_to_fahrenheit(double c) nogil:
return c * 9.0 / 5.0 + 32.0
# 复杂函数不适合内联(增加代码体积,可能反而慢)
cdef inline int fibonacci(int n): # 不建议内联递归
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)

最佳实践:

  • 1-3行简单运算适合内联
  • 有循环的函数内联收益小
  • 递归函数绝对不要内联

功能说明:内联函数不能引用Python对象。

# 错误:内联函数不能返回Python对象
# cdef inline str get_name(): # 编译错误
# return "Alice"
# 正确:纯C类型内联
cdef inline int get_id(): # OK
return 42
# 内联函数可以用于nogil块
cdef inline double hypot(double x, double y) nogil:
return (x * x + y * y) ** 0.5

功能说明:Python函数调用需要获取GIL,有额外开销。

# Python函数调用需要GIL
cdef int call_python_func(object func, int x):
# 获取GIL - 开销约50-100ns
return func(x) # 调用Python函数

开销说明:每次Python函数调用需要:

  1. 获取GIL(~50ns)
  2. 执行Python函数
  3. 释放GIL

功能说明:减少Python函数调用次数或用C代码替代。

# 批量调用 - 多次GIL获取
cdef int batch_process(object func, list items):
cdef int i
cdef int result = 0
cdef int n = len(items)
for i in range(n):
result += func(items[i]) # 每次循环都要GIL
return result
# 优化:避免Python函数调用
cdef int fast_batch_process(object func, list items):
cdef int i
cdef int result = 0
cdef int n = len(items)
cdef int x
for i in range(n):
x = items[i]
result += x * x # 纯C运算,无Python调用
return result

性能对比:

方式耗时(100万元素)
batch_process~200ms
fast_batch_process~20ms

功能说明:缓存函数属性减少属性查找开销。

# 缓存函数属性
cdef class OptimizedCallback:
cdef object _func
cdef object _name
def __init__(self, func):
self._func = func
self._name = func.__name__ # 缓存属性访问
cdef int call(self, int x) nogil:
# nogil块中不能调用Python函数
# 但可以使用缓存的属性
return len(self._name) # OK,C级操作

功能说明:使用__cinit__进行模块级初始化,避免重复初始化。

module.pyx
import numpy as np
cdef int initialized = 0
cdef double[:] cached_data
def __cinit__():
global initialized, cached_data
if not initialized:
cached_data = np.zeros(1000)
initialized = 1
def process(int index, double value):
global cached_data
cached_data[index] = value
return cached_data[index]

输出示例:

>>> from module import process
>>> process(0, 3.14)
3.14

最佳实践:__cinit__比__init__更适合资源分配,因为可能调用多次。

功能说明:声明模块级C变量,用于全局状态或缓存。

# 模块级C变量
cdef int global_counter = 0
cdef double global_cache[1000]
# 线程安全计数器(警告:非真正线程安全)
cdef void increment():
global global_counter
global_counter += 1
# 模块级常量
cpdef int MAX_BUFFER_SIZE = 4096
cpdef double PI = 3.141592653589793

常见坑:

  • 模块级变量不是线程安全的
  • 多进程环境下不共享

功能说明:使用类管理全局状态,比裸变量更安全。

# 全局状态类
cdef class GlobalState:
cdef int _initialized
cdef dict _cache
cdef list _handlers
def __cinit__(self):
self._initialized = 0
self._cache = {}
self._handlers = []
cpdef void initialize(self):
if not self._initialized:
self._cache = {}
self._handlers = []
self._initialized = 1
cpdef void register_handler(self, object handler):
self._handlers.append(handler)
# 全局单例
global_state = GlobalState()

输出示例:

>>> from module import global_state
>>> global_state.initialize()
>>> global_state.register_handler(lambda x: x * 2)

优化技术性能提升适用场景
参数类型声明3-10x所有函数
返回值类型声明1-2x所有函数
inline函数1-5x简单运算/热点
避免Python调用5-10x循环内调用
模块级缓存2-100x重复计算
情况推荐方案
Python调用函数cpdef + 类型声明
Cython内部函数cdef inline
简单运算inline + nogil
全局状态单例类管理

  1. 编写sum函数,对比有无参数类型声明的性能差异
  2. 用inline优化矩阵乘法中的元素运算
  3. 实现一个函数缓存装饰器,缓存函数结果
  4. 创建全局状态类,管理配置和缓存
  5. 对比cpdef调用cdef vs cpdef调用cpdef的性能差异
  6. 统计1000次循环中Python函数调用和纯C运算的时间占比