Numpy: Умножение больших массивов с dtype = int8 - SLOW

Рассмотрим следующий фрагмент кода, который генерирует какой-то (потенциально) огромный многомерный массив и выполняет numpy.tensordot с ним (numpy.tensordot ли мы один и тот же или два разных массива здесь, на самом деле не имеет значения).

import time
import numpy

L, N = 6, 4

shape = (2*L)*[N,]
A = numpy.arange(numpy.prod(shape)).reshape(shape)
A = A % 256 - 128   # [-127,+127]
axes=(range(1,2*L,2), range(0,2*L,2))

def run(dtype, repeat=1):
    A_ = A.astype(dtype)
    t = time.time()
    for i in range(repeat):
        numpy.tensordot(A_, A_, axes)
    t = time.time() - t
    print(dtype, '   \t%8.2f sec\t%8.2f MB' %(t, A_.nbytes/1e6))

Теперь мы можем сравнить производительность для разных типов данных, например:

run(numpy.float64)
run(numpy.int64)

Поскольку массив состоит только из небольших целых чисел, я хотел бы сохранить некоторую память, используя dtype=int8. Однако это замедляет матричное умножение A LOT.

Вот некоторые примеры тестов

Первый, важный для моего варианта использования. Остальные только для справки. Использование Numpy 1.13.1 и Python 3.4.2

Большой массив

L, N = 6, 4; A.size = 4**12 = 16777216
<class 'numpy.float64'>        59.58 sec      134.22 MB
<class 'numpy.float32'>        44.19 sec       67.11 MB
<class 'numpy.int16'>         711.16 sec       33.55 MB
<class 'numpy.int8'>          647.40 sec       16.78 MB

Тот же массив с разными типами данных. Память уменьшается, как ожидалось. Но почему большие различия в процессорном времени? Если бы я ожидал, что int будет быстрее, чем float.

Большой массив с различной формой

L, N = 1, 4**6; A.size = (4**6)**2 = 16777216
<class 'numpy.float64'>        57.95 sec      134.22 MB
<class 'numpy.float32'>        42.84 sec       67.11 MB

Форма не имеет большого эффекта.

Не так большой массив

L, N = 5, 4
<class 'numpy.float128'>       10.91 sec       16.78 MB
<class 'numpy.float64'>         0.98 sec        8.39 MB
<class 'numpy.float32'>         0.90 sec        4.19 MB
<class 'numpy.float16'>         9.80 sec        2.10 MB
<class 'numpy.int64'>           8.84 sec        8.39 MB
<class 'numpy.int32'>           5.55 sec        4.19 MB
<class 'numpy.int16'>           2.23 sec        2.10 MB
<class 'numpy.int8'>            1.82 sec        1.05 MB

Меньшие значения, но та же странная тенденция.

малый массив, много повторений

L, N = 2, 4; A.size = 4 ** 4 = 256; повторить = 1000000

<class 'numpy.float128'>       17.92 sec        4.10 KB
<class 'numpy.float64'>        14.20 sec        2.05 KB
<class 'numpy.float32'>        12.21 sec        1.02 KB
<class 'numpy.float16'>        41.72 sec        0.51 KB
<class 'numpy.int64'>          14.21 sec        2.05 KB
<class 'numpy.int32'>          14.26 sec        1.02 KB
<class 'numpy.int16'>          13.88 sec        0.51 KB
<class 'numpy.int8'>           13.03 sec        0.26 KB

Кроме того, что float16 намного медленнее, все в порядке.

Вопрос

Почему int8 для очень больших массивов настолько медленнее? Есть ли способ обойти это? Сохранение памяти становится все более важным для больших массивов!

Ответ 1

К несчастью,

как правильно подчеркнуто в комментариях, "движок" за кулисами BLAS, и у него нет собственного целочисленного типа. Вот почему Float64 или 32 будут работать быстрее (некоторые обсуждения в соответствующем ответе для аналогичного вопроса для C++).

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