Производительность между операциями массива C-смежных и Fortran-смежных

Ниже я сравнивал производительность при работе с суммами операций между C-смежными и Fortran-смежными массивами (C против заказа памяти FORTRAN). Я установил axis=0, чтобы номера были добавлены по столбцу. Я был удивлен, что Fortran-смежный массив на самом деле медленнее, чем его аналог C. Разве это не тот массив Fortran-смежности, который имеет смежное распределение памяти в столбцах и, следовательно, лучше работает в столбце?

import numpy as np
a = np.random.standard_normal((10000, 10000))
c = np.array(a, order='C')
f = np.array(a, order='F')

В ноутбуке Jupyter запустите

%timeit c.sum(axis=0)
10 loops, best of 3: 84.6 ms per loop
%timeit f.sum(axis=0)
10 loops, best of 3: 137 ms per loop

Ответ 1

Я думаю, что это в реализации np.sum(). Например:

import numpy as np

A = np.random.standard_normal((10000,10000))
C = np.array(A, order='C')
F = np.array(A, order='F')

Бенчмаркинг с Ipython:

In [7]: %timeit C.sum(axis=0)
10 loops, best of 3: 101 ms per loop

In [8]: %timeit C.sum(axis=1)
10 loops, best of 3: 149 ms per loop

In [9]: %timeit F.sum(axis=0)
10 loops, best of 3: 149 ms per loop

In [10]: %timeit F.sum(axis=1)
10 loops, best of 3: 102 ms per loop

Таким образом, он ведет себя точно так же, как и ожидалось. Но попробуйте другую функцию:

In [17]: %timeit np.amax(C, axis=0)
1 loop, best of 3: 173 ms per loop

In [18]: %timeit np.amax(C, axis=1)
10 loops, best of 3: 70.4 ms per loop

In [13]: %timeit np.amax(F,axis=0)
10 loops, best of 3: 72 ms per loop

In [14]: %timeit np.amax(F,axis=1)
10 loops, best of 3: 168 ms per loop

Конечно, это яблоки для апельсинов. Но np.amax() работает вдоль оси, как и сумма и возвращает вектор с одним элементом для каждой строки/столбца. И ведет себя так, как можно было бы ожидать.

In [25]: C.strides
Out[25]: (80000, 8)

In [26]: F.strides
Out[26]: (8, 80000)

Сообщает нам, что массивы фактически упакованы в порядок строк, а порядок столбцов и цикл в этом направлении должны быть намного быстрее. Если, например, сумма не суммирует каждую строку за строкой, поскольку она перемещается вдоль столбцов для обеспечения суммы столбца (ось = 0). Но без середины подглядывания внутри .pyd я просто размышляю.

EDIT:

От ссылки percusse: http://docs.scipy.org/doc/numpy/reference/generated/numpy.ufunc.reduce.html

Уменьшает размерность единицы, применяя ufunc вдоль одной оси.

Пусть a.shape = (N_0,..., N_i,..., N_ {M-1}). Затем ufunc.reduce(a, axis = i) [k_0,.., k_ {i-1}, k_ {i + 1},.., k_ {M-1}] = результат итерации j по диапазону ( N_i), кумулятивно применяя ufunc к каждому a [k_0,.., k_ {i-1}, j, k_ {i + 1},.., k_ {M-1}]

Итак, в псевдокоде при вызове F.sum(axis = 0):

for j=cols #axis=0
    for i=rows #axis=1
        sum(j,i)=F(j,i)+sum(j-1,i)

Таким образом, на самом деле он будет перебирать строку при вычислении суммы столбца, значительно замедляя ее, когда он находится в основном столбце. Такое поведение объясняет разницу.

eric link предоставляет нам реализацию, для кого-то достаточно любопытного, чтобы пройти через большое количество кода по этой причине.

Ответ 2

Это ожидалось. Если вы проверите результат

%timeit f.sum(axis=1)

он также дает аналогичный результат со временем c. Аналогично,

%timeit c.sum(axis=1)

медленнее.


Некоторое объяснение: предположим, что у вас есть следующая структура

|1| |6|
|2| |7|
|3| |8|
|4| |9|
|5| |10|

Как упоминал Эрик, эти операции работают с reduce. Пусть говорят, что мы запрашиваем сумму столбца. Таким образом, интуитивный механизм не воспроизводится так, что каждый столбец обращается один раз, суммируется и записывается. На самом деле это противоположность, к которой обращается каждая строка, и функция (здесь суммирование) выполняется по существу аналогично наличию двух массивов a,b и выполнению

a += b

Это очень неформальный способ повторить то, что упоминается сверх-критически в документации по сокращению. Для этого требуется, чтобы строки были доступны со смещением, хотя мы выполняем сумму столбца [1,6] + [2,7] + [3,8]... Следовательно, направление реализации имеет значение в зависимости от операции, но не от массива.