寻求条件分支的 Cython 优化:是否有等效的切换?

问题描述 投票:0回答:1

我目前正在开发一个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

等具有
不同的参数列表(无论是参数数量还是名称),这使得该方法相当不优雅。

python c switch-statement cython compiler-optimization
1个回答
0
投票
Cython 通常旨在实现 Python 语法+C 类型,因此并不是真正旨在添加新的流程控制功能。因此,没有适合您所需的特定功能。

但是,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
 可能不起作用)。

此优化可以通过

optimize.use_switch

 编译器指令控制,但默认情况下处于启用状态。

显然,问题在于这是一个相当不透明的优化,如果 Cython 由于某种原因没有做到这一点,您不会收到任何警告。但同样,我不认为

O(1)

 switch 语句是由 C 或 C++ 保证的,所以你还依赖那里不透明的编译器东西。


关于

match/case

的一些注意事项:

    它并不是被设计为交换机的替代品 - 它更多的是为了更多地解包更复杂的嵌套
  • dict/list
     结构。它类似开关的部分是完整行为的构建块,而不是预期的主要用途。
  • 它尚未在 Cython 主分支中实现,
  • 如果您想尝试一下,可以在
  • patma-preview
     分支中找到。
    
      这是数千行未经审查的代码,因此请谨慎对待,
    • 没有努力让该分支保持最新状态,或提供轮子或类似的东西,所以它是在 DIY 的基础上,
    • 简单的
    • match/case
       块应该以与链式 if 语句相同的方式转换为 switch。因此,如果您更喜欢语法,您可以获得上述优化。
© www.soinside.com 2019 - 2024. All rights reserved.