【问题标题】:Cythonize a Python function to make it fasterCythonize 一个 Python 函数以使其更快
【发布时间】:2011-02-02 09:33:08
【问题描述】:

几周前,我问了一个关于提高用 Python 编写的函数的速度的问题。当时,TryPyPy 让我注意到了使用 Cython 这样做的可能性。他还好心地举了一个例子,说明我如何对代码 sn-p 进行 Cythonize。我想对下面的代码做同样的事情,看看我可以通过声明变量类型来多快。我有几个与此相关的问题。我在 cython.org 上看过教程,但我仍有一些问题。它们密切相关:

  1. 我不懂任何 C。我需要学习哪些部分才能使用 Cython 声明变量类型?
  2. python列表和元组对应的C类型是什么?例如,我可以在 Cython 中将 double 用于 Python 中的 float。我要为列表做什么?一般来说,我在哪里可以找到给定 Python 类型对应的 C 类型。

我如何对下面的代码进行 Cythonize 的任何示例都会非常有帮助。我在代码中插入了 cmets,提供有关变量类型的信息。

class Some_class(object):
    ** Other attributes and functions **
    def update_awareness_status(self, this_var, timePd):
        '''Inputs: this_var (type: float)
           timePd (type: int)
           Output: None'''

        max_number = len(self.possibilities)
        # self.possibilities is a list of tuples.
        # Each tuple is a pair of person objects. 

        k = int(math.ceil(0.3 * max_number))
        actual_number = random.choice(range(k))
        chosen_possibilities = random.sample(self.possibilities, 
                                         actual_number)
        if len(chosen_possibilities) > 0:
            # chosen_possibilities is a list of tuples, each tuple is a pair
            # of person objects. I have included the code for the Person class
            # below.
            for p1,p2 in chosen_possibilities:

                # awareness_status is a tuple (float, int)
                if p1.awareness_status[1] < p2.awareness_status[1]:                   
                    if p1.value > p2.awareness_status[0]:
                        p1.awareness_status = (this_var, timePd)
                    else:
                        p1.awareness_status = p2.awareness_status
                elif p1.awareness_status[1] > p2.awareness_status[1]:
                    if p2.value > p1.awareness_status[0]:
                        p2.awareness_status = (price, timePd)
                    else:
                        p2.awareness_status = p1.awareness_status
                else:
                    pass     

class Person(object):                                         
    def __init__(self,id, value):
        self.value = value
        self.id = id
        self.max_val = 50000
        ## Initial awareness status.          
        self.awarenessStatus = (self.max_val, -1)

【问题讨论】:

  • 你有一个可以工作的纯 Python 代码吗?你有没有分析过它的执行?大部分时间都花在了哪里?
  • 列表和元组的类型只有listtuple。 C 只定义了几种类型,主要是数字,所以几乎所有其他类型都使用与 Python 中相同的名称。
  • @eat:是的,我分析了代码,发现大部分时间都花在了上面的函数上。你是在问这个函数中大部分时间花在哪里?整个代码运行需要 47 秒,上述代码功能需要 22 秒。它被访问 79900 次。谢谢!
  • @kwatford:谢谢。这很有帮助。是否有一个很好的参考来谈论这些事情。我在 Cython 网站上没有找到这个。
  • @Curious2learn:我没见过。为了优化使用 Cython,您可能需要了解 C 以及 Python 的 C-API 的一些工作原理。其余的大部分都没有掌握这些知识。我认为其中一些内容没有很好记录的原因是设计师和大多数目标受众都非常熟悉它。很少有设计师会认为它需要说出来,也很少有用户需要说出来。如果您认为需要更多文档,我会在 Cython 的邮件列表中提出。

标签: python performance cython


【解决方案1】:

作为一般说明,您可以通过运行带有-a“annotate”选项的cython 命令准确查看 Cython 为每个源代码行生成的 C 代码。有关示例,请参阅 Cython documentation。这在尝试查找函数体中的瓶颈时非常很有帮助。

此外,在 Cython 处理您的代码时,还有"early binding for speed" 的概念。 Python 对象(例如下面的 Person 类的实例)使用通用 Python 代码进行属性访问,这在内部循环中很慢。我怀疑如果您将Person 类更改为cdef class,那么您会看到一些加速。此外,您需要在内部循环中键入 p1p2 对象。

由于您的代码有很多 Python 调用(例如random.sample),除非您找到一种方法将这些行放入 C 中,否则您可能不会获得巨大的加速,这需要大量的努力。

您可以将内容输入为tuplelist,但这通常并不意味着加快速度。最好尽可能使用 C 数组;您必须查找的内容。

通过下面的简单修改,我得到了 1.6 倍的加速。请注意,我必须在这里和那里更改一些内容才能编译。

ctypedef int ITYPE_t

cdef class CyPerson:
    # These attributes are placed in the extension type's C-struct, so C-level
    # access is _much_ faster.
    cdef ITYPE_t value, id, max_val
    cdef tuple awareness_status

    def __init__(self, ITYPE_t id, ITYPE_t value):
        # The __init__ function is much the same as before.
        self.value = value
        self.id = id
        self.max_val = 50000
        ## Initial awareness status.          
        self.awareness_status = (self.max_val, -1)

NPERSONS = 10000

import math
import random

class Some_class(object):

    def __init__(self):
        ri = lambda: random.randint(0, 10)
        self.possibilities = [(CyPerson(ri(), ri()), CyPerson(ri(), ri())) for i in range(NPERSONS)]

    def update_awareness_status(self, this_var, timePd):
        '''Inputs: this_var (type: float)
           timePd (type: int)
           Output: None'''

        cdef CyPerson p1, p2
        price = 10

        max_number = len(self.possibilities)
        # self.possibilities is a list of tuples.
        # Each tuple is a pair of person objects. 

        k = int(math.ceil(0.3 * max_number))
        actual_number = random.choice(range(k))
        chosen_possibilities = random.sample(self.possibilities,
                                         actual_number)
        if len(chosen_possibilities) > 0:
            # chosen_possibilities is a list of tuples, each tuple is a pair
            # of person objects. I have included the code for the Person class
            # below.
            for persons in chosen_possibilities:
                p1, p2 = persons
                # awareness_status is a tuple (float, int)
                if p1.awareness_status[1] < p2.awareness_status[1]:
                    if p1.value > p2.awareness_status[0]:
                        p1.awareness_status = (this_var, timePd)
                    else:
                        p1.awareness_status = p2.awareness_status
                elif p1.awareness_status[1] > p2.awareness_status[1]:
                    if p2.value > p1.awareness_status[0]:
                        p2.awareness_status = (price, timePd)
                    else:
                        p2.awareness_status = p1.awareness_status

【讨论】:

    【解决方案2】:

    C 并不直接知道列表的概念。 基本数据类型是intcharshortlong)、float/double(所有这些类型都非常简单地映射到 python)和指针。 如果您对指针的概念不熟悉,请查看:Wikipedia:Pointers

    在某些情况下,指针可以用作元组/数组替换。字符指针是所有字符串的基础。 假设您有一个整数数组,然后将其存储为具有起始地址的连续内存块,您定义类型(int)并且它是一个指针(*):

    cdef int * array;
    

    现在您可以像这样访问数组的每个元素:

    array[0] = 1
    

    但是,必须分配内存(例如,使用 malloc)并且高级索引将不起作用(例如,array[-1] 将是内存中的随机数据,这也适用于超出保留空间宽度的索引)。

    更复杂的类型不直接映射到 C,但通常有一种 C 方法可以做一些可能不需要 python 类型的事情(例如,for 循环不需要范围数组/迭代器)。

    正如您自己所注意到的,编写好的 cython 代码需要更详细的 C 知识,因此继续学习教程可能是最好的下一步。

    【讨论】:

    • @rumpel:感谢您的回复。我将开始学习一些C。我希望C中不存在相当于python元组和列表的类型这一事实,并不意味着当我将函数blah更改为Cython时,我也必须更改输出blah用作输入的数据的其他函数。
    • @Curious 不,通常不会。您通常可以在 C 中访问 python 类型的信息,或者如果这仍然不够快,有时在 cython 函数处理之前和之后转换数据类型是有意义的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    • 2013-12-03
    • 1970-01-01
    • 2021-10-08
    • 1970-01-01
    相关资源
    最近更新 更多