Нарезка массива CuPy 300 МБ примерно в 5 раз медленнее, чем NumPy - PullRequest
1 голос
/ 31 марта 2020

Мой код включает в себя разбиение массивов 432x432x400 в общей сложности ~ 10 миллионов раз для генерации пакетов данных для обучения нейронной сети. Поскольку это довольно большие массивы (92 миллиона точек данных / 300 МБ), я надеялся ускорить это с помощью CuPy (и, возможно, даже ускорить обучение, генерируя данные на том же графическом процессоре, что и обучение), но обнаружил, что это на самом деле делает код В 5 раз медленнее.

Это ожидаемое поведение из-за накладных расходов CuPy или я что-то упустил?

Код для воспроизведения:

import cupy as cp
import numpy as np
import timeit
cp_arr = cp.zeros((432, 432, 400), dtype=cp.float32)
np_arr = np.zeros((432, 432, 400), dtype=np.float32)

# numbers below are representative of my code
cp_code = 'arr2 = cp_arr[100:120, 100:120, 100:120]'
np_code = 'arr2 = np_arr[100:120, 100:120, 100:120]'

timeit.timeit(cp_code, number=8192*4, globals=globals())  # prints 0.122
timeit.timeit(np_code, number=8192*4, globals=globals())  # prints 0.027

Настройка:

  • Графический процессор: NVIDIA Quadro P4000

  • CuPy Версия: 7.3.0

  • ОС: CentOS Linux 7

  • Версия CUDA: 10.1

  • cuDNN Версия: 7.6.5

Ответы [ 2 ]

2 голосов
/ 02 апреля 2020

Врезка в NumPy и CuPy фактически не копирует данные куда-либо, а просто возвращает новый массив, в котором данные совпадают, но с указателем, смещенным к первому элементу нового среза и скорректированной форме. Обратите внимание, как исходный массив и срез имеют одинаковые шаги:

In [1]: import cupy as cp

In [2]: a = cp.zeros((432, 432, 400), dtype=cp.float32)

In [3]: b = a[100:120, 100:120, 100:120]

In [4]: a.strides
Out[4]: (691200, 1600, 4)

In [5]: b.strides
Out[5]: (691200, 1600, 4)

То же самое можно проверить, заменив CuPy на NumPy.

. Для реальной операции нарезки наиболее надежным способом сделать это было бы добавление .copy() к каждой операции, тем самым обеспечивая доступ к памяти / копирование в память:

cp_code = 'arr2 = cp_arr[100:120, 100:120, 100:120].copy()'  # 0.771 seconds
np_code = 'arr2 = np_arr[100:120, 100:120, 100:120].copy()'  # 0.154 seconds

К сожалению, для приведенного выше шаблона памяти плохо для графических процессоров, так как маленькие куски не смогут насытить каналы памяти, поэтому все равно медленнее, чем NumPy. Однако CuPy может быть намного быстрее, если порции способны приблизиться к насыщению канала памяти, например:

cp_code = 'arr2 = cp_arr[:, 100:120, 100:120].copy()'  # 0.786 seconds
np_code = 'arr2 = np_arr[:, 100:120, 100:120].copy()'  # 2.911 seconds
2 голосов
/ 01 апреля 2020

Я также подтвердил, что нарезка в кружочках примерно в 5 раз медленнее, хотя есть более точный способ измерения времени (см., Например, https://github.com/cupy/cupy/pull/2740).

Размер массив не имеет значения, потому что операции среза не копируют данные, а создают представления. Результат со следующим выглядит следующим образом:

cp_arr = cp.zeros((4, 4, 4), dtype=cp.float32)
cp_code = 'arr2 = cp_arr[1:3, 1:3, 1:3]'

Естественно, что «возьмите фрагмент, а затем отправьте его в графический процессор» быстрее, потому что это уменьшает количество байтов для передачи. Подумайте об этом, если первым препроцессом является нарезка.

...