Ответ 1
Я приведу из docs документы
Память похожа на текущую поддержку буфера массива NumPy (
np.ndarray[np.float64_t, ndim=2]
), но у них больше функций и более чистый синтаксис.
Это указывает на то, что разработчики Cython считают, что представления памяти являются современными.
Представления памяти имеют некоторые большие преимущества перед нотами np.ndarray
в первую очередь в элегантности и функциональной совместимости, однако они не превосходят производительность.
Производительность:
Во-первых, следует отметить, что boundscheck иногда не работает с представлениями памяти, что приводит к искусственно быстрым цифрам для memviews с boundscheck = True (т.е. вы получаете быстрое, небезопасное индексирование), если вы полагаетесь на boundscheck, чтобы поймать ошибки, которые могли бы быть неприятным сюрпризом.
В большинстве случаев, когда были применены оптимизации компилятора, представления памяти и нотация массива numpy равны по производительности, часто именно так. Когда есть разница, это обычно не более 10-30%.
Тест производительности
Число - это время в секундах для выполнения 100 000 000 операций. Меньше быстрее.
ACCESS+ASSIGNMENT on small array (10000 elements, 10000 times)
Results for `uint8`
1) memory view: 0.0415 +/- 0.0017
2) np.ndarray : 0.0531 +/- 0.0012
3) pointer : 0.0333 +/- 0.0017
Results for `uint16`
1) memory view: 0.0479 +/- 0.0032
2) np.ndarray : 0.0480 +/- 0.0034
3) pointer : 0.0329 +/- 0.0008
Results for `uint32`
1) memory view: 0.0499 +/- 0.0021
2) np.ndarray : 0.0413 +/- 0.0005
3) pointer : 0.0332 +/- 0.0010
Results for `uint64`
1) memory view: 0.0489 +/- 0.0019
2) np.ndarray : 0.0417 +/- 0.0010
3) pointer : 0.0353 +/- 0.0017
Results for `float32`
1) memory view: 0.0398 +/- 0.0027
2) np.ndarray : 0.0418 +/- 0.0019
3) pointer : 0.0330 +/- 0.0006
Results for `float64`
1) memory view: 0.0439 +/- 0.0037
2) np.ndarray : 0.0422 +/- 0.0013
3) pointer : 0.0353 +/- 0.0013
ACCESS PERFORMANCE (100,000,000 element array):
Results for `uint8`
1) memory view: 0.0576 +/- 0.0006
2) np.ndarray : 0.0570 +/- 0.0009
3) pointer : 0.0061 +/- 0.0004
Results for `uint16`
1) memory view: 0.0806 +/- 0.0002
2) np.ndarray : 0.0882 +/- 0.0005
3) pointer : 0.0121 +/- 0.0003
Results for `uint32`
1) memory view: 0.0572 +/- 0.0016
2) np.ndarray : 0.0571 +/- 0.0021
3) pointer : 0.0248 +/- 0.0008
Results for `uint64`
1) memory view: 0.0618 +/- 0.0007
2) np.ndarray : 0.0621 +/- 0.0014
3) pointer : 0.0481 +/- 0.0006
Results for `float32`
1) memory view: 0.0945 +/- 0.0013
2) np.ndarray : 0.0947 +/- 0.0018
3) pointer : 0.0942 +/- 0.0020
Results for `float64`
1) memory view: 0.0981 +/- 0.0026
2) np.ndarray : 0.0982 +/- 0.0026
3) pointer : 0.0968 +/- 0.0016
ASSIGNMENT PERFORMANCE (100,000,000 element array):
Results for `uint8`
1) memory view: 0.0341 +/- 0.0010
2) np.ndarray : 0.0476 +/- 0.0007
3) pointer : 0.0402 +/- 0.0001
Results for `uint16`
1) memory view: 0.0368 +/- 0.0020
2) np.ndarray : 0.0368 +/- 0.0019
3) pointer : 0.0279 +/- 0.0009
Results for `uint32`
1) memory view: 0.0429 +/- 0.0022
2) np.ndarray : 0.0427 +/- 0.0005
3) pointer : 0.0418 +/- 0.0007
Results for `uint64`
1) memory view: 0.0833 +/- 0.0004
2) np.ndarray : 0.0835 +/- 0.0011
3) pointer : 0.0832 +/- 0.0003
Results for `float32`
1) memory view: 0.0648 +/- 0.0061
2) np.ndarray : 0.0644 +/- 0.0044
3) pointer : 0.0639 +/- 0.0005
Results for `float64`
1) memory view: 0.0854 +/- 0.0056
2) np.ndarray : 0.0849 +/- 0.0043
3) pointer : 0.0847 +/- 0.0056
Код контрольной точки (отображается только для доступа + назначение)
# cython: boundscheck=False
# cython: wraparound=False
# cython: nonecheck=False
import numpy as np
cimport numpy as np
cimport cython
# Change these as desired.
data_type = np.uint64
ctypedef np.uint64_t data_type_t
cpdef test_memory_view(data_type_t [:] view):
cdef Py_ssize_t i, j, n = view.shape[0]
for j in range(0, n):
for i in range(0, n):
view[i] = view[j]
cpdef test_ndarray(np.ndarray[data_type_t, ndim=1] view):
cdef Py_ssize_t i, j, n = view.shape[0]
for j in range(0, n):
for i in range(0, n):
view[i] = view[j]
cpdef test_pointer(data_type_t [:] view):
cdef Py_ssize_t i, j, n = view.shape[0]
cdef data_type_t * data_ptr = &view[0]
for j in range(0, n):
for i in range(0, n):
(data_ptr + i)[0] = (data_ptr + j)[0]
def run_test():
import time
from statistics import stdev, mean
n = 10000
repeats = 100
a = np.arange(0, n, dtype=data_type)
funcs = [('1) memory view', test_memory_view),
('2) np.ndarray', test_ndarray),
('3) pointer', test_pointer)]
results = {label: [] for label, func in funcs}
for r in range(0, repeats):
for label, func in funcs:
start=time.time()
func(a)
results[label].append(time.time() - start)
print('Results for `{}`'.format(data_type.__name__))
for label, times in sorted(results.items()):
print('{: <14}: {:.4f} +/- {:.4f}'.format(label, mean(times), stdev(times)))
Эти тесты показывают, что в целом нет большой разницы в производительности. Иногда нотация np.ndarray выполняется немного быстрее, а иногда и вице-верка.
Одна вещь, с которой нужно следить за бенчмарками, заключается в том, что когда код становится немного более сложным или "реалистичным", разница внезапно исчезает, как будто компилятор теряет уверенность в применении некоторой очень умной оптимизации. Это можно увидеть с помощью производительности поплавков, где нет никакой разницы, предположительно, поскольку некоторые фантастические целые оптимизации не могут быть использованы.
Простота использования
Представления памяти имеют существенные преимущества, например, вы можете использовать представление памяти в массиве numpy, массиве CPython, массиве cython, массиве c и т.д., как настоящем, так и будущем. Существует также простой параллельный синтаксис для добавления чего-либо в представление памяти:
cdef double [:, :] data_view = <double[:256, :256]>data
В этом отношении видны большие возможности памяти, потому что, если вы введете функцию в качестве вида памяти, тогда она может принять любую из этих вещей. Это означает, что вы можете написать модуль, который не имеет зависимости от numpy, но который все еще может принимать несколько массивов.
С другой стороны, нотация np.ndarray
приводит к тому, что все еще является массивом numpy, и вы можете вызывать все методы массива numpy на нем. Не имеет большого значения иметь как массив numpy, так и представление о массиве, хотя:
def dostuff(arr):
cdef double [:] arr_view = arr
# Now you can use 'arr' if you want array functions,
# and arr_view if you want fast indexing
Как и массив, так и представление массива прекрасно работают на практике, и мне очень нравится стиль, поскольку он делает четкое различие между методами уровня на уровне python и методами уровня c.
Заключение
Производительность очень близка, и, конечно же, нет достаточной разницы для того, чтобы быть решающим фактором.
Нотация numpy-массива приближается к идеалу ускорения кода python, не меняя его, поскольку вы можете продолжать использовать одну и ту же переменную, получая при этом полную индексацию массива.
С другой стороны, обозначение представления памяти, вероятно, является будущим. Если вам нравится его элегантность и использование контейнеров с различными типами данных, а не только массивы numpy, существует очень веская причина для использования соображений памяти для обеспечения согласованности.