Cythonize функцию Python, чтобы сделать ее быстрее

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

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

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

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)

Ответ 1

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

Кроме того, существует концепция "раннее связывание для скорости" при Cython-ing вашем коде. Объект 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 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 напрямую не знает концепцию списков. Основными типами данных являются int (char, short, long), float/double (все из которых имеют довольно простые сопоставления с python) и указатели. Если концепция указателей для вас не новая, взгляните на: Википедия: указатели

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

cdef int * array;

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

array[0] = 1

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

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

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