Самый эффективный способ построить 1-й массив / список / вектор неизвестной длины, используя Cython?Или это никогда не должно быть сделано? - PullRequest
9 голосов
/ 13 сентября 2011

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

from libc.math cimport log
def main(some args):
    cdef (some vars)

    cdef list OutputList = []

    # NB: all vars have declared types
    for x in range(t):
        (do some Cythonic stuff, some of which uses my cimport-ed log)
        if condition is True:
            OutputList.append(x) # this is the only 'yellow' line in my main loop.
    return OutputList # return Python object to Python script that calls main()

К сожалению, я не знаю длинымоего выходного массива / списка / вектора (что бы я в итоге не использовал).Тем не менее, я мог бы установить его на 52560, и это то, что я в итоге изменил его размер, чтобы перейти к следующему коду Python.Я хотел бы получить значительное увеличение скорости без установки длины выходного массива, но я с радостью оставлю эту надежду, если она сдерживает меня.

Я также пытался использовать C ++ в Cython, чтобы использовать структуры данных C ++ (вектор, очередь и т. Д.), Но это лишает меня возможности красиво импортировать журнал.В документации / вики по Cython я вижу, что вы можете написать модуль 'shim' для использования функций на чистом C в C ++ Cython, но я понятия не имею, как это сделать, и я не могу ничего найти о том, как это сделать.

В любом случае, я приветствую все предложения, которые придерживаются моего вопроса:

Каков наилучший способ построения списка / массива / вектора неизвестного размера в Cython?Или есть четкая альтернатива (например, урегулирование с помощью итеративного объекта известной длины), которая делает спор моей проблемой неизвестной длины?

Обновление

Контейнеры C ++ продемонстрировали увеличение скорости по сравнению с назначением элементов, а назначение элементов действительно показало увеличение скорости по сравнению с добавлением в списки и массивы.Лучший способ - использовать контейнеры C ++, в то же время имея возможность импортировать функции pure-C ... это предотвратит замедление от необходимости искать за пределами libc.math функцию быстрой регистрации.

Ответы [ 3 ]

2 голосов
/ 22 сентября 2011

build1darray.pyx :

#cython: boundscheck=False, wraparound=False
from libc.math cimport log

from cython.parallel cimport prange

import numpy as pynp
cimport numpy as np

# copy declarations from libcpp.vector to allow nogil
cdef extern from "<vector>" namespace "std":
    cdef cppclass vector[T]:
        void push_back(T&) nogil
        size_t size()
        T& operator[](size_t)

def makearray(int t):
    cdef vector[np.float_t] v
    cdef int i
    with nogil: 
        for i in range(t):
            if i % 10 == 0:
                v.push_back(log(i+1))

    cdef np.ndarray[np.float_t] a = pynp.empty(v.size(), dtype=pynp.float)
    for i in prange(a.shape[0], nogil=True):
        a[i] = v[i]
    return a

2-я частьсоставляет ~ 1% от первого цикла, поэтому нет смысла оптимизировать его для скорости в этом случае.

<math.h> имеет extern "C" { ... } в моей системе, поэтому libc.math.log работает.

PyArray_SimpleNewFromData() может быть использовано, чтобы избежать копирования данных за счет затрат на управление памятью для массива самостоятельно.

2 голосов
/ 13 сентября 2011

Добавление списков Python - это хорошо оптимизированная операция в CPython. Python не выделяет память для каждого элемента, но постепенно увеличивает массивы указателей на объекты в списке. Так что простое переключение на Cython вам здесь не очень поможет.

Вы можете использовать контейнеры c ++ в Cython следующим образом:

from libc.math cimport log
from libcpp.list cimport list as cpplist

def main(int t):

    cdef cpplist[int] temp

    for x in range(t):
        if x> 0:
            temp.push_back(x)

    cdef int N = temp.size()
    cdef list OutputList = N*[0]

    for i in range(N):
        OutputList[i] = temp.front()
        temp.pop_front()

    return OutputList  

Вы должны проверить, не ускоряет ли это все, но, возможно, вы не наберете много скорости.

Другой способ - использовать массивы numpy. Здесь Cython очень хорош в оптимизации кода. Поэтому, если вы можете жить с пустым массивом в качестве возвращаемого значения main, вы должны учитывать это и заменить конструкцию и заполнение OutputList некоторым кодом Cython, выделяющим и заполняющим массив Numpy.

Для получения дополнительной информации см. http://docs.cython.org/src/tutorial/numpy.html

Спросите, нужна ли вам помощь.

ОБНОВЛЕНИЕ : код должен быть немного быстрее, если вы избегаете поиска метода в обоих циклах:

from libc.math cimport log
from libcpp.list cimport list as cpplist

def main(int t):

    cdef cpplist[int] temp

    push_back = temp.push_back
    for x in range(t):
        if x> 0:
            push_back(x)

    cdef int N = temp.size()
    cdef list OutputList = N*[0]

    front = temp.front()
    pop_front = temp.pop_front()
    for i in range(N):
        OutputList[i] = front()
        pop_front()

    return OutputList  
0 голосов
/ 13 сентября 2011

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

# pseudo code
def main(): 
   count = 0
   for i in range(t):
       if criteria: 
            count += 1

   cdef numpy.ndarray[count] result

   int idx =0
   for i in range(t):
      if criteria:
          idx += 1
          result[idx] = value
...