Cythonize функция Python, чтобы сделать это быстрее - PullRequest
12 голосов
/ 02 февраля 2011

Несколько недель назад я задал вопрос об увеличении скорости функции, написанной на Python. В то время TryPyPy обратил мое внимание на возможность использования Cython для этого. Он также любезно привел пример того, как я мог бы Cythonize этот фрагмент кода. Я хочу сделать то же самое с кодом ниже, чтобы увидеть, как быстро я могу сделать это, объявляя типы переменных. У меня есть пара вопросов, связанных с этим. Я видел Учебник на cython.org, но у меня все еще есть некоторые вопросы. Они тесно связаны:

  1. Я не знаю C. Какие части мне нужно изучить, чтобы использовать Cython для объявления типов переменных?
  2. Какой тип C соответствует спискам и кортежам Python? Например, я могу использовать double в Cython для float в Python. Что я делаю для списков? В общем, где я могу найти соответствующий тип C для данного типа Python.

Любой пример того, как я мог бы Cythonize приведенный ниже код, был бы действительно полезным. Я вставил в код комментарии, которые дают информацию о типе переменной.

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)

Ответы [ 2 ]

7 голосов
/ 17 февраля 2011

Как общее примечание, вы можете точно увидеть, что C-код генерирует Cython для каждой строки исходного кода, выполнив команду cython с опцией -a "annotate". Смотрите Cython документация для примеров. Это чрезвычайно полезно при попытке найти узкие места в теле функции.

Также существует концепция «раннее связывание для скорости» при Cython-обработке вашего кода. Объект Python (как и экземпляры вашего Person класса ниже) использует общий код Python для доступа к атрибутам, который работает медленно во внутреннем цикле. Я подозреваю, что если вы измените класс Person на cdef class, то вы увидите некоторое ускорение. Кроме того, вам нужно набрать объекты p1 и p2 во внутреннем цикле.

Поскольку в вашем коде много вызовов Python (например, random.sample), вы, скорее всего, не добьетесь огромного ускорения, если не найдете способ поместить эти строки в C, что требует значительных усилий.

Вы можете набирать вещи как tuple или list, но это не означает значительного ускорения. Лучше использовать массивы 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
1 голос
/ 03 февраля 2011

C напрямую не знает концепцию списков. Основными типами данных являются int (char, short, long), float / double (все из которых имеют довольно простые отображения на python) и указатели. Если концепция указателей является новой для вас, взгляните на: Wikipedia: Pointers

В некоторых случаях указатели могут использоваться как замены кортежей / массивов. Указатели символов являются основой для всех строк. Допустим, у вас есть массив целых чисел, затем вы должны сохранить его как непрерывный кусок памяти с начальным адресом, определить тип (int) и указатель (*):

cdef int * array;

Теперь вы можете получить доступ к каждому элементу массива следующим образом:

array[0] = 1

Однако память должна быть выделена (например, с использованием malloc), и расширенное индексирование не будет работать (например, array[-1] будет случайными данными в памяти, это также относится к индексам, превышающим ширину зарезервированного пространства).

Более сложные типы напрямую не отображаются в C, но часто есть способ C сделать что-то, что может не потребовать типов python (например, для цикла for не требуется массив / итератор диапазона).

Как вы сами заметили, для написания хорошего кода на Cython требуется более детальное знание языка C, поэтому, пожалуй, лучше всего перейти к обучающей программе.

...