在Python脚本中调用Cython函数时意外的性能损失?

3 投票
2 回答
1547 浏览
提问于 2025-04-16 23:50

我在一个Python脚本中有一段对时间要求很高的代码,所以我决定写一个Cython模块(只需要一个函数)来替代它。不幸的是,我从Cython模块调用的函数在我的Python脚本中执行的速度,远没有我在其他场景中测试的那么快。需要注意的是,我不能分享代码,因为合同法的原因!下面是一些情况,可以作为我问题的初步描述:

(1) 通过Python解释器导入模块并运行函数,执行Cython函数。运行得相对快(大约0.04秒,经过约100次独立测试,而原来的大约是0.24秒)。

(2) 在Python脚本的“全局”级别调用Cython函数(也就是说,不在任何函数内部)。速度和情况(1)一样。

(3) 在Python脚本中调用Cython函数,把Cython函数放在我Python脚本的主函数里;在全局和局部命名空间中测试,速度和情况(1)一样。

(4) 和(3)一样,但在这个Python函数中的一个简单for循环里。速度和情况(1)一样。

(5) 问题来了! 和(4)一样,但在另一个for循环里:Cython函数的执行时间(无论是全局调用还是局部调用)膨胀到其他情况的约10倍,而我正需要在这里调用这个函数。这个循环没有什么奇怪的地方,我测试了这个循环的所有组件(调整/移除我能调整的部分)。我还尝试用'while'循环来试试,但没有效果。

“我还没试过的一个方法是把这个最内层的循环做成一个函数,然后再看看。” 编辑:刚试过这个-没成功。

感谢你们的任何建议-我非常遗憾不能分享我的代码……这让我有点心痛,但我的客户就是不希望这段代码被外泄。如果你需要我提供其他信息,请告诉我!

-真正的问题和一个初步(丑陋的)解决方案-

结果发现,这种情况下最明显的提示(和往常一样)是:问题并不是出在for循环上;为什么会呢?经过几次测试后,很明显我调用Cython函数的方式有问题,因为我可以在其他地方调用它(使用一个与“真实”Cython函数不同的输入变量),而没有性能下降的问题。

根本问题:数据类型。我写的Cython函数是期望接收一个充满标准浮点数的列表。不幸的是,我的代码是这样做的:

function_input = list(numpy_array_containing_npfloat64_data) # yuck.
type(function_input[0]) = numpy.float64
output = Cython_Function(function_input)

在Cython函数内部:

def Cython_Function(list function_input):
    cdef many_vars
    """process lots of vars expecting C floats""" # Slowness from converting numpy.float64's --> floats???
    type(output) = list
    return output

我知道我可以在Cython函数中更多地玩弄数据类型,我可能会这样做,以避免必须“列出”一个现有的numpy数组。无论如何,这里是我当前的解决方案:

function_input = [float(x) for x in function_input]

我欢迎任何反馈和改进建议。function_input的numpy数组其实不需要numpy.float64的精度,但在传递给我的Cython函数之前,它确实会被用几次。

2 个回答

1
function_input = list(numpy_array_containing_npfloat64_data)

def Cython_Function(list function_input):
    cdef many_vars
def Cython_Function(np.ndarray[dtype=np.float64] input):
   ....

我觉得问题出在把numpy数组当成列表来用……你不能把np.ndarray直接作为Cython函数的输入吗?

3

可能是这样的:虽然每次调用Cython实现的函数时,它的速度比对应的Python函数快,但在调用Cython函数时会有更多的额外开销,因为它需要在模块的命名空间中查找函数的名字。你可以先把这个函数赋值给一个本地可调用的变量,举个例子:

from module import function

def main():
    my_func = functon
    for i in sequence:
        my_func()

如果可以的话,最好把循环放在Cython函数里面,这样可以把Python循环的额外开销减少到一个(非常小的)编译过的C循环的开销。我明白这可能不太容易做到(比如需要从全局或更大的范围获取引用),但你可以试着研究一下这个问题。祝你好运!

撰写回答