Python / Cython: накладные расходы с классами, хранящимися в массивах numpy

#python #performance #function #optimization #cython

#python #Производительность #функция #оптимизация #cython

Вопрос:

Этот медленный код можно улучшить, изменив структуру, но иногда это сложно обойти. Я думаю, причина кроется в классах, хранящихся в массиве. Я слышал, что представления памяти используются для связывания массивов python и c, но я все еще новичок в этом (только некоторые знания python).

Есть ли способ эффективно выполнить следующее?

Пример класса:

 cdef class ClassWithAdditionFunction:
    cdef double value

    def __init__(self, double value):
        self.value = value

    cpdef add_one(self):
        self.value  = 1
  

Медленная функция:

 cdef unsigned long int i, ii
cdef unsigned long int loops = pow(10, 8)
cdef double value

addition_classes = np.array([None] * 10)

for i in range(len(addition_classes)):
    addition_classes[i] = ClassWithAdditionFunction(value=0)

for i in range(loops/10):
    for ii in range(10):
        addition_classes[ii].add_one()
  

Большое вам спасибо за любые предложения!

Ответ №1:

Есть несколько небольших вещей, которые вы могли бы сделать, которые должны немного помочь. На самом деле строка кода, которую вы хотите ускорить, это addition_classes[ii].add_one() . Если вы используете cython -a , чтобы увидеть, что на самом деле происходит под капотом, вы увидите, что вы вызываете Pyx_GetItemInt , затем PyObject_GetAttr , затем PyObject_Call . Вы хотите структурировать свой код, чтобы избежать этих 3 вызовов.

Чтобы избежать вызова GetItem, вы захотите использовать либо интерфейс буфера numpy, либо представления памяти. Это сообщает cython структуру вашего массива и позволяет ему более эффективно извлекать элементы из массива. В приведенном ниже примере я использовал представление памяти. Если вы делаете что-то подобное, убедитесь, что массив на самом деле является массивом, полным экземпляров ClassWithAdditionFunction , иначе вы, скорее всего, получите ошибку segfault.

Чтобы избежать вызова GetAttr, объявите переменную типа ClassWithAdditionFunction и выполните вызовы метода для этой переменной, чтобы cython знал, что у переменной есть скомпилированная версия метода, которую он может использовать для более быстрых вызовов.

Наконец, вы уже определили add_one с помощью метода cpdef, но я бы предложил также добавить возвращаемый тип. Обычно мы могли бы просто поместить void , но поскольку это функция cpdef, а не функция cdef, вы можете вместо этого использовать int .

Если вы соберете все это вместе, это должно выглядеть примерно так:

 import numpy as np
cimport cython

cdef class ClassWithAdditionFunction:
    cdef double value

    def __init__(self, double value):
        self.value = value

    cpdef int add_one(self):
        self.value  = 1
        return 0

@cython.boundscheck(False)
@cython.wraparound(False)
def main():

    cdef:
        unsigned long int i, ii, loops = 10 ** 6
        ClassWithAdditionFunction addInstance
        double value, y

    addition_classes = np.array([None] * 10)
    cdef ClassWithAdditionFunction[:] arrayview = addition_classes

    for i in range(len(addition_classes)):
        addition_classes[i] = ClassWithAdditionFunction(value=0)

    for i in range(loops/10):
        for ii in range(10):
            addInstance = arrayview[ii]
            addInstance.add_one()

    return None
  

Комментарии:

1. Это на удивление быстро! Большое вам спасибо!

2. @user3465201 вы можете принять ответ, нажав на стрелку слева…