Каков рекомендуемый способ выделения памяти для типизированного представления памяти?
Документация Cython по типизированным представлениям памяти перечисляет три способа присвоения типизированному представлению памяти:
- из необработанного указателя C,
- из
np.ndarray
а также - из
cython.view.array
,
Предположим, что у меня нет данных, переданных в мою функцию Cython извне, но вместо этого я хочу выделить память и вернуть ее как np.ndarray
Какой из этих вариантов я выбрал? Также предположим, что размер этого буфера не является константой времени компиляции, т. Е. Я не могу выделить в стеке, но потребуется malloc
для варианта 1.
Таким образом, 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
на первом месте?
cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32)
2 ответа
Смотрите здесь для ответа.
Основная идея в том, что вы хотите 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
из-за множества забавных методов, прикрепленных к объектам. Вот и все.
Как продолжение ответа Veedrac: знайте, используя memoryview
поддержка cpython.array
с Python 2.7 в настоящее время приводит к утечке памяти. Это, кажется, давняя проблема, поскольку она упоминается в списке рассылки пользователей cython здесь в сообщении от ноября 2012 года. Запуск эталонного скрипта Veedrac с Cython версии 0.22 с Python 2.7.6 и Python 2.7.9 приводит к большая утечка памяти при инициализации cpython.array
используя либо buffer
или же memoryview
интерфейс. При запуске скрипта с Python 3.4 утечки памяти не происходит. Я отправил отчет об ошибке в список рассылки разработчиков Cython.