Подтвердить что ты не робот

Каков рекомендуемый способ выделения памяти для типизированной памяти?

Документация Cython по типизированным представлениям памяти перечисляет три способа присвоения типизированному представлению памяти:

  1. из необработанного указателя C,
  2. от np.ndarray и
  3. из cython.view.array.

Предположим, что у меня нет данных, переданных в мою функцию cython извне, но вместо этого я хочу выделить память и вернуть ее в виде np.ndarray, какой из этих вариантов я выбрал? Также предположим, что размер этого буфера не является константой времени компиляции, т.е. я не могу выделить его в стеке, но для варианта 1 потребуется malloc.

Таким образом, 3 варианта будут выглядеть примерно так:

from libc.stdlib cimport malloc, free
cimport numpy as np
from cython cimport view

np.import_array()

def memview_malloc(int N):
    cdef int * m = <int *>malloc(N * sizeof(int))
    cdef int[::1] b = <int[:N]>m
    free(<void *>m)

def memview_ndarray(int N):
    cdef int[::1] b = np.empty(N, dtype=np.int32)

def memview_cyarray(int N):
    cdef int[::1] b = view.array(shape=(N,), itemsize=sizeof(int), format="i")

Что меня удивляет, так это то, что во всех трех случаях Cython генерирует довольно много кода для выделения памяти, в частности, вызов __Pyx_PyObject_to_MemoryviewSlice_dc_int. Это говорит о том (и я могу ошибаться, мое понимание внутренней работы Cython очень ограничено), что он сначала создает объект Python, а затем "встраивает" его в представление памяти, что кажется ненужным.

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

Какой из трех методов рекомендуется? Или есть другой, лучший вариант?

Дополнительный вопрос: я хочу, наконец, вернуть результат в виде np.ndarray, после работы с этим представлением памяти в функции. Является ли типизированное представление памяти лучшим выбором или я бы просто использовал старый интерфейс буфера, как ndarray ниже, для создания ndarray в первую очередь?

cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32)
4b9b3361

Ответ 1

Посмотрите здесь для ответа.

Основная идея заключается в том, что вы хотите cpython.array.array и cpython.array.clone ( не cython.array.*):

from cpython.array cimport array, clone

# This type is what you want and can be cast to things of
# the "double[:]" syntax, so no problems there
cdef array[double] armv, templatemv

templatemv = array('d')

# This is fast
armv = clone(templatemv, L, False)

ИЗМЕНИТЬ

Оказалось, что этапы в этой теме были мусором. Здесь мой набор, с моими таймингами:

# cython: language_level=3
# cython: boundscheck=False
# cython: wraparound=False

import time
import sys

from cpython.array cimport array, clone
from cython.view cimport array as cvarray
from libc.stdlib cimport malloc, free
import numpy as numpy
cimport numpy as numpy

cdef int loops

def timefunc(name):
    def timedecorator(f):
        cdef int L, i

        print("Running", name)
        for L in [1, 10, 100, 1000, 10000, 100000, 1000000]:
            start = time.clock()
            f(L)
            end = time.clock()
            print(format((end-start) / loops * 1e6, "2f"), end=" ")
            sys.stdout.flush()

        print("μs")
    return timedecorator

print()
print("INITIALISATIONS")
loops = 100000

@timefunc("cpython.array buffer")
def _(int L):
    cdef int i
    cdef array[double] arr, template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cpython.array memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr
    cdef array template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cpython.array raw C type")
def _(int L):
    cdef int i
    cdef array arr, template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("numpy.empty_like memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr
    template = numpy.empty((L,), dtype='double')

    for i in range(loops):
        arr = numpy.empty_like(template)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("malloc")
def _(int L):
    cdef int i
    cdef double* arrptr

    for i in range(loops):
        arrptr = <double*> malloc(sizeof(double) * L)
        free(arrptr)

    # Prevents dead code elimination
    str(arrptr[0])

@timefunc("malloc memoryview")
def _(int L):
    cdef int i
    cdef double* arrptr
    cdef double[::1] arr

    for i in range(loops):
        arrptr = <double*> malloc(sizeof(double) * L)
        arr = <double[:L]>arrptr
        free(arrptr)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cvarray memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr

    for i in range(loops):
        arr = cvarray((L,),sizeof(double),'d')

    # Prevents dead code elimination
    str(arr[0])



print()
print("ITERATING")
loops = 1000

@timefunc("cpython.array buffer")
def _(int L):
    cdef int i
    cdef array[double] arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("cpython.array memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("cpython.array raw C type")
def _(int L):
    cdef int i
    cdef array arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("numpy.empty_like memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = numpy.empty((L,), dtype='double')

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("malloc")
def _(int L):
    cdef int i
    cdef double* arrptr = <double*> malloc(sizeof(double) * L)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arrptr[i]

    free(arrptr)

    # Prevents dead-code elimination
    str(d)

@timefunc("malloc memoryview")
def _(int L):
    cdef int i
    cdef double* arrptr = <double*> malloc(sizeof(double) * L)
    cdef double[::1] arr = <double[:L]>arrptr

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    free(arrptr)

    # Prevents dead-code elimination
    str(d)

@timefunc("cvarray memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = cvarray((L,),sizeof(double),'d')

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

Вывод:

INITIALISATIONS
Running cpython.array buffer
0.100040 0.097140 0.133110 0.121820 0.131630 0.108420 0.112160 μs
Running cpython.array memoryview
0.339480 0.333240 0.378790 0.445720 0.449800 0.414280 0.414060 μs
Running cpython.array raw C type
0.048270 0.049250 0.069770 0.074140 0.076300 0.060980 0.060270 μs
Running numpy.empty_like memoryview
1.006200 1.012160 1.128540 1.212350 1.250270 1.235710 1.241050 μs
Running malloc
0.021850 0.022430 0.037240 0.046260 0.039570 0.043690 0.030720 μs
Running malloc memoryview
1.640200 1.648000 1.681310 1.769610 1.755540 1.804950 1.758150 μs
Running cvarray memoryview
1.332330 1.353910 1.358160 1.481150 1.517690 1.485600 1.490790 μs

ITERATING
Running cpython.array buffer
0.010000 0.027000 0.091000 0.669000 6.314000 64.389000 635.171000 μs
Running cpython.array memoryview
0.013000 0.015000 0.058000 0.354000 3.186000 33.062000 338.300000 μs
Running cpython.array raw C type
0.014000 0.146000 0.979000 9.501000 94.160000 916.073000 9287.079000 μs
Running numpy.empty_like memoryview
0.042000 0.020000 0.057000 0.352000 3.193000 34.474000 333.089000 μs
Running malloc
0.002000 0.004000 0.064000 0.367000 3.599000 32.712000 323.858000 μs
Running malloc memoryview
0.019000 0.032000 0.070000 0.356000 3.194000 32.100000 327.929000 μs
Running cvarray memoryview
0.014000 0.026000 0.063000 0.351000 3.209000 32.013000 327.890000 μs

(Причина для эталона "итераций" заключается в том, что некоторые методы имеют удивительно разные характеристики в этом отношении.)

В порядке скорости инициализации:

malloc: Это суровый мир, но он быстро. Если вам нужно выделить много вещей и иметь беспрепятственную итерацию и производительность индексации, это должно быть так. Но, как правило, вы делаете хорошую ставку для...

cpython.array raw C type: Ну, черт возьми, это быстро. И это безопасно. К сожалению, через Python он получает доступ к своим полям данных. Вы можете избежать этого, используя замечательный трюк:

arr.data.as_doubles[i]

который доводит его до стандартной скорости при устранении безопасности! Это делает эту замечательную замену для malloc, будучи в основном довольно-ориентированной версией!

cpython.array buffer: При входе всего в три-четыре раза больше времени установки malloc, это выглядит замечательно. К сожалению, он имеет значительные накладные расходы (хотя и небольшие по сравнению с директивами boundscheck и wraparound). Это означает, что он действительно конкурирует с вариантами полной безопасности, но это самый быстрый из них для инициализации. Ваш выбор.

cpython.array memoryview: теперь это на порядок медленнее, чем malloc для инициализации. Это позор, но он повторяется так же быстро. Это стандартное решение, которое я хотел бы предложить, если boundscheck или wraparound не находятся на (в этом случае cpython.array buffer может быть более убедительным компромисс).

Остальное. Единственное, что стоит что-то, это numpy, из-за множества методов удовольствия, связанных с объектами. Что это, тем не менее.

Ответ 2

В ответ на ответ Veedrac: знайте, что поддержка memoryview cpython.array с python 2.7, по-видимому, приводит к утечкам памяти в настоящее время. Это, кажется, давняя проблема, поскольку она упоминается в списке рассылки cython-users здесь в сообщении от ноября 2012 года. Запуск тестового теста Veedrac с Cython версии 0.22 с Python 2.7.6 и Python 2.7.9 приводит к большой утечке памяти при инициализации cpython.array с использованием интерфейса buffer или memoryview. При запуске script с Python 3.4 не происходит утечки памяти. Я отправил отчет об ошибке в список рассылки разработчиков Cython.