как скопировать два массива 2d numpy в предварительно выделенный массив - PullRequest
1 голос
/ 16 апреля 2020

У меня есть два больших массива 2d numpy с одинаковым количеством строк, но с разным количеством столбцов. Допустим, arr1 имеет форму (num_rows1, num_cols1), а arr2 имеет форму (num_rows1, num_cols2).

Я предварительно выделил numpy массив arr12 размера (num_rows1, num_cols1 + num_cols2).

Каков наиболее эффективный способ скопировать arr1 и arr2 в arr12, так что arr1 объединяется с arr2?

Является ли использование этого метода предварительного размещения более эффективным, чем метод конкатенации numpy?

Ответы [ 2 ]

1 голос
/ 16 апреля 2020

Сравнительный анализ

Мы просто проведем сравнительный анализ различных наборов данных и сделаем из них выводы.

Сроки

Использование пакета benchit (несколько сравнительных тестов инструменты, упакованные вместе; отказ от ответственности: я являюсь ее автором) для сравнения предлагаемых решений.

Код для сравнительного анализа:

import numpy as np
import benchit

def numpy_concatenate(a, b):
    return np.concatenate((a,b),axis=1)

def numpy_hstack(a, b):
    return np.hstack((a,b))

def preallocate(a, b):
    m,n = a.shape[1], b.shape[1]
    out = np.empty((a.shape[0],m+n), dtype=np.result_type((a.dtype, b.dtype)))
    out[:,:m] = a
    out[:,m:] = b
    return out

funcs = [numpy_concatenate, numpy_hstack, preallocate]
R = np.random.rand 

inputs = {n: (R(1000,1000), R(1000,n)) for n in [100, 200, 500, 1000, 200, 5000]}
t = benchit.timings(funcs, inputs, multivar=True,   input_name='Col length of b')
t.plot(logy=False, logx=True, savepath='plot_1000rows.png')

enter image description here

Заключение : Они сопоставимы по времени.

Профилирование памяти

На стороне памяти np.hstack должно быть похоже на np.concatenate. Итак, мы будем использовать один из них.

Давайте настроим входной набор данных с большими двумерными массивами. Мы проведем некоторое тестирование памяти.

Код установки:

# Filename : memprof_npconcat_preallocate.py
import numpy as np
from memory_profiler import profile

@profile(precision=10)
def numpy_concatenate(a, b):
    return np.concatenate((a,b),axis=1)

@profile(precision=10)
def preallocate(a, b):
    m,n = a.shape[1], b.shape[1]
    out = np.empty((a.shape[0],m+n), dtype=np.result_type((a.dtype, b.dtype)))
    out[:,:m] = a
    out[:,m:] = b
    return out

R = np.random.rand
a,b = R(1000,1000), R(1000,1000)

if __name__ == '__main__':
    numpy_concatenate(a, b)

if __name__ == '__main__':
    preallocate(a, b)  

Итак, a равен 1000x1000 и то же самое для b.

Выполнить:

$ python3 -m memory_profiler memprof_npconcat_preallocate.py 
Filename: memprof_npconcat_preallocate.py

Line #    Mem usage    Increment   Line Contents
================================================
     9  69.3281250000 MiB  69.3281250000 MiB   @profile(precision=10)
    10                             def numpy_concatenate(a, b):
    11  84.5546875000 MiB  15.2265625000 MiB       return np.concatenate((a,b),axis=1)


Filename: memprof_npconcat_preallocate.py

Line #    Mem usage    Increment   Line Contents
================================================
    13  69.3554687500 MiB  69.3554687500 MiB   @profile(precision=10)
    14                             def preallocate(a, b):
    15  69.3554687500 MiB   0.0000000000 MiB       m,n = a.shape[1], b.shape[1]
    16  69.3554687500 MiB   0.0000000000 MiB       out = np.empty((a.shape[0],m+n), dtype=np.result_type((a.dtype, b.dtype)))
    17  83.6484375000 MiB  14.2929687500 MiB       out[:,:m] = a
    18  84.4218750000 MiB   0.7734375000 MiB       out[:,m:] = b
    19  84.4218750000 MiB   0.0000000000 MiB       return out

Таким образом, для метода preallocate общее потребление памяти составляет 14.2929687500 + 0.7734375000, что немного меньше, чем 15.2265625000.

Изменение размеров входных массивов на 5000x5000 как для a, так и b -

$ python3 -m memory_profiler memprof_npconcat_preallocate.py
Filename: memprof_npconcat_preallocate.py

Line #    Mem usage    Increment   Line Contents
================================================
     9 435.4101562500 MiB 435.4101562500 MiB   @profile(precision=10)
    10                             def numpy_concatenate(a, b):
    11 816.8515625000 MiB 381.4414062500 MiB       return np.concatenate((a,b),axis=1)


Filename: memprof_npconcat_preallocate.py

Line #    Mem usage    Increment   Line Contents
================================================
    13 435.5351562500 MiB 435.5351562500 MiB   @profile(precision=10)
    14                             def preallocate(a, b):
    15 435.5351562500 MiB   0.0000000000 MiB       m,n = a.shape[1], b.shape[1]
    16 435.5351562500 MiB   0.0000000000 MiB       out = np.empty((a.shape[0],m+n), dtype=np.result_type((a.dtype, b.dtype)))
    17 780.3203125000 MiB 344.7851562500 MiB       out[:,:m] = a
    18 816.9296875000 MiB  36.6093750000 MiB       out[:,m:] = b
    19 816.9296875000 MiB   0.0000000000 MiB       return out

Опять же, общая сумма из предварительного распределения меньше.

Вывод: метод предварительного распределения имеет несколько лучшие преимущества памяти, что в некотором смысле имеет смысл , С concatenate у нас есть три массива, задействованных src1 + src2 -> dst, тогда как с предварительным распределением есть только sr c и dst с меньшей перегрузкой памяти, хотя и в два этапа.

1 голос
/ 16 апреля 2020

numpy скомпилированный код, такой как concatenate, обычно определяет размер необходимого возвращаемого массива, создает этот массив и копирует в него значения. Тот факт, что он делает это с вызовами C -API, не имеет никакого значения в использовании памяти. concatenate не перезаписывает и не использует повторно память, использованную аргументами.

In [465]: A, B = np.ones((1000,1000)), np.zeros((1000,500))

при некоторых сравнениях по времени:

In [466]: timeit np.concatenate((A,B), axis=1)                                                         
6.73 ms ± 338 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [467]: C = np.zeros((1000,1500))                                                                    
In [468]: timeit np.concatenate((A,B), axis=1, out=C)                                                  
6.44 ms ± 174 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [469]: %%timeit 
     ...: C = np.zeros((1000,1500)) 
     ...: np.concatenate((A,B), axis=1, out=C)                                                                                               
11.5 ms ± 358 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [470]: %%timeit 
     ...: C = np.zeros((1000,1500)) 
     ...: C[:,:1000]=A; C[:,1000:]=B                                                                                             
11.5 ms ± 282 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [471]: %%timeit 
     ...: C[:,:1000]=A; C[:,1000:]=B                                                                                              
6.29 ms ± 160 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...