我目前正在开发一个Python项目,我需要在Cython中重写该项目以提高性能。在这段Python代码中,有一段使用一系列
if/elif
语句来确定任务的执行路径,涉及数百个分支。此外,这一段代码将被重复执行。我正在寻找一种在 Cython 中重写此代码的方法,它可能会提供类似于其他语言中的 switch
语句的性能改进。
对于上下文,像 Java 这样的语言实现 switch 语句的方式是,其时间复杂度对于 tableswitch
可以是 O(1),对于
lookupswitch
可以是 O(log n),具体取决于情况的稀疏性。这是通过编译器优化来实现的,而链式
if/else
if 语句的线性时间复杂度 (O(n)) 中不存在这种优化。同样,C/C++ 编译器可以优化 switch 语句来增强性能(通过跳转表 O(1) 和二分查找 O(log n))。 鉴于 Cython 将代码编译为 C,我想知道 Cython 中是否存在类似于 switch 语句的语法或构造,可能受益于编译器优化以避免线性时间复杂度。 Cython 是否提供任何此类功能,或者是否有推荐的实践来在处理大量条件分支时实现类似的性能改进?
请注意,在 Python 3.10 中,引入了类似于 switch 语句的语法,称为 match/case。然而,它缺乏编译器优化,保持了 O(n) 的时间复杂度。
我也考虑过使用Python字典,将每个if/elif
分支封装成一个单独的函数来确定分支函数,如下所示:
func_map = {
0: func0,
1: func1,
3: func3,
10: func10,
}
但是,我发现使用Python字典的开销太大了。此外,函数func0, func1, func3
等具有不同的参数列表(无论是参数数量还是名称),这使得该方法相当不优雅。
但是,Cython 确实已经识别了简单的链式
if/elif
并将它们转换为 C:中的 switch 语句
def f(int i):
if i==0:
print("aaa")
elif i==5 or i==7:
print("bbb")
elif i==9:
print("ccc")
else:
print("ddd")
更改为:
switch (__pyx_v_i) {
case 0:
__pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_print, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
break;
case 5:
case 7:
__pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_print, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 5, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
break;
case 9:
__pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_print, __pyx_tuple__3, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 7, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
break;
default:
__pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_print, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 9, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
break;
}
请注意,更复杂的情况i==5 or i==7
也翻译正确。为了最大程度地发挥此作用,参数(此处为
i
)需要是整数/枚举类型,并且您要比较的值应该是整数文字或枚举值。您也不应该在
if
语句中包含额外的逻辑(例如,
elif i==9 and something_else
可能不起作用)。此优化可以通过 编译器指令控制,但默认情况下处于启用状态。 显然,问题在于这是一个相当不透明的优化,如果 Cython 由于某种原因没有做到这一点,您不会收到任何警告。但同样,我不认为
O(1)
switch 语句是由 C 或 C++ 保证的,所以你还依赖那里不透明的编译器东西。
match/case
的一些注意事项:
dict/list
结构。它类似开关的部分是完整行为的构建块,而不是预期的主要用途。
patma-preview
分支中找到。
match/case
块应该以与链式 if 语句相同的方式转换为 switch。因此,如果您更喜欢语法,您可以获得上述优化。