лучший способ сохранить массивы на диске - PullRequest
101 голосов
/ 08 марта 2012

Я ищу быстрый способ сохранить большие массивы. Я хочу сохранить их на диск в двоичном формате, а затем относительно быстро прочитать их обратно в память. cPickle, к сожалению, недостаточно быстр.

Я нашел numpy.savez и numpy.load . Но странность в том, что numpy.load загружает файл npy в «карту памяти». Это означает, что регулярные манипуляции с массивами действительно медленные. Например, что-то вроде этого будет очень медленным:

#!/usr/bin/python
import numpy as np;
import time; 
from tempfile import TemporaryFile

n = 10000000;

a = np.arange(n)
b = np.arange(n) * 10
c = np.arange(n) * -0.5

file = TemporaryFile()
np.savez(file,a = a, b = b, c = c);

file.seek(0)
t = time.time()
z = np.load(file)
print "loading time = ", time.time() - t

t = time.time()
aa = z['a']
bb = z['b']
cc = z['c']
print "assigning time = ", time.time() - t;

точнее, первая строка будет действительно быстрой, но остальные строки, которые присваивают массивам obj, смехотворно медленны:

loading time =  0.000220775604248
assining time =  2.72940087318

Есть ли какой-нибудь лучший способ сохранить массивы numpy? В идеале я хочу иметь возможность хранить несколько массивов в одном файле.

Ответы [ 6 ]

158 голосов
/ 02 января 2017

Я сравнил производительность (пространство и время) для ряда способов хранения массивов.Лишь немногие из них поддерживают несколько массивов на файл, но, возможно, это полезно в любом случае.

benchmark for numpy array storage

Npy и двоичные файлы очень быстрые и маленькие для плотных данных.Если данные разрежены или очень структурированы, вы можете использовать npz со сжатием, что сэкономит много места, но потребует некоторого времени загрузки.

Если переносимость является проблемой, двоичный файл лучше, чем npy.Если удобочитаемость для человека важна, вам придется пожертвовать большой производительностью, но ее можно добиться довольно хорошо, используя csv (который, конечно, также очень переносим).

Более подробная информация и код доступныв репозиторий GitHub .

45 голосов
/ 08 марта 2012

Я большой поклонник hdf5 для хранения больших массивов. Есть два варианта работы с hdf5 в python:

http://www.pytables.org/

http://www.h5py.org/

Оба предназначены для эффективной работы с массивами numpy.

36 голосов
/ 05 марта 2014

Теперь существует клон на основе HDF5 pickle с именем hickle!

https://github.com/telegraphic/hickle

import hickle as hkl 

data = { 'name' : 'test', 'data_arr' : [1, 2, 3, 4] }

# Dump data to file
hkl.dump( data, 'new_data_file.hkl' )

# Load data from file
data2 = hkl.load( 'new_data_file.hkl' )

print( data == data2 )

РЕДАКТИРОВАТЬ:

Существует также возможность "засолки" непосредственно в сжатый архив, выполнив:

import pickle, gzip, lzma, bz2

pickle.dump( data, gzip.open( 'data.pkl.gz',   'wb' ) )
pickle.dump( data, lzma.open( 'data.pkl.lzma', 'wb' ) )
pickle.dump( data,  bz2.open( 'data.pkl.bz2',  'wb' ) )

compression


Приложение

import numpy as np
import matplotlib.pyplot as plt
import pickle, os, time
import gzip, lzma, bz2, h5py

compressions = [ 'pickle', 'h5py', 'gzip', 'lzma', 'bz2' ]
labels = [ 'pickle', 'h5py', 'pickle+gzip', 'pickle+lzma', 'pickle+bz2' ]
size = 1000

data = {}

# Random data
data['random'] = np.random.random((size, size))

# Not that random data
data['semi-random'] = np.zeros((size, size))
for i in range(size):
    for j in range(size):
        data['semi-random'][i,j] = np.sum(data['random'][i,:]) + np.sum(data['random'][:,j])

# Not random data
data['not-random'] = np.arange( size*size, dtype=np.float64 ).reshape( (size, size) )

sizes = {}

for key in data:

    sizes[key] = {}

    for compression in compressions:

        if compression == 'pickle':
            time_start = time.time()
            pickle.dump( data[key], open( 'data.pkl', 'wb' ) )
            time_tot = time.time() - time_start
            sizes[key]['pickle'] = ( os.path.getsize( 'data.pkl' ) * 10**(-6), time_tot )
            os.remove( 'data.pkl' )

        elif compression == 'h5py':
            time_start = time.time()
            with h5py.File( 'data.pkl.{}'.format(compression), 'w' ) as h5f:
                h5f.create_dataset('data', data=data[key])
            time_tot = time.time() - time_start
            sizes[key][compression] = ( os.path.getsize( 'data.pkl.{}'.format(compression) ) * 10**(-6), time_tot)
            os.remove( 'data.pkl.{}'.format(compression) )

        else:
            time_start = time.time()
            pickle.dump( data[key], eval(compression).open( 'data.pkl.{}'.format(compression), 'wb' ) )
            time_tot = time.time() - time_start
            sizes[key][ labels[ compressions.index(compression) ] ] = ( os.path.getsize( 'data.pkl.{}'.format(compression) ) * 10**(-6), time_tot )
            os.remove( 'data.pkl.{}'.format(compression) )


f, ax_size = plt.subplots()
ax_time = ax_size.twinx()

x_ticks = labels
x = np.arange( len(x_ticks) )

y_size = {}
y_time = {}
for key in data:
    y_size[key] = [ sizes[key][ x_ticks[i] ][0] for i in x ]
    y_time[key] = [ sizes[key][ x_ticks[i] ][1] for i in x ]

width = .2
viridis = plt.cm.viridis

p1 = ax_size.bar( x-width, y_size['random']       , width, color = viridis(0)  )
p2 = ax_size.bar( x      , y_size['semi-random']  , width, color = viridis(.45))
p3 = ax_size.bar( x+width, y_size['not-random']   , width, color = viridis(.9) )

p4 = ax_time.bar( x-width, y_time['random']  , .02, color = 'red')
ax_time.bar( x      , y_time['semi-random']  , .02, color = 'red')
ax_time.bar( x+width, y_time['not-random']   , .02, color = 'red')

ax_size.legend( (p1, p2, p3, p4), ('random', 'semi-random', 'not-random', 'saving time'), loc='upper center',bbox_to_anchor=(.5, -.1), ncol=4 )
ax_size.set_xticks( x )
ax_size.set_xticklabels( x_ticks )

f.suptitle( 'Pickle Compression Comparison' )
ax_size.set_ylabel( 'Size [MB]' )
ax_time.set_ylabel( 'Time [s]' )

f.savefig( 'sizes.pdf', bbox_inches='tight' )
14 голосов
/ 09 марта 2012

savez () сохраняет данные в zip-файле, может потребоваться некоторое время, чтобы сжать и разархивировать файл. Вы можете использовать функцию save () и load ():

f = file("tmp.bin","wb")
np.save(f,a)
np.save(f,b)
np.save(f,c)
f.close()

f = file("tmp.bin","rb")
aa = np.load(f)
bb = np.load(f)
cc = np.load(f)
f.close()

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

7 голосов
/ 06 марта 2014

Еще одна возможность эффективного хранения массивов - это Bloscpack :

#!/usr/bin/python
import numpy as np
import bloscpack as bp
import time

n = 10000000

a = np.arange(n)
b = np.arange(n) * 10
c = np.arange(n) * -0.5
tsizeMB = sum(i.size*i.itemsize for i in (a,b,c)) / 2**20.

blosc_args = bp.DEFAULT_BLOSC_ARGS
blosc_args['clevel'] = 6
t = time.time()
bp.pack_ndarray_file(a, 'a.blp', blosc_args=blosc_args)
bp.pack_ndarray_file(b, 'b.blp', blosc_args=blosc_args)
bp.pack_ndarray_file(c, 'c.blp', blosc_args=blosc_args)
t1 = time.time() - t
print "store time = %.2f (%.2f MB/s)" % (t1, tsizeMB / t1)

t = time.time()
a1 = bp.unpack_ndarray_file('a.blp')
b1 = bp.unpack_ndarray_file('b.blp')
c1 = bp.unpack_ndarray_file('c.blp')
t1 = time.time() - t
print "loading time = %.2f (%.2f MB/s)" % (t1, tsizeMB / t1)

и вывод для моего ноутбука (относительно старого MacBook Air с процессором Core2):

$ python store-blpk.py
store time = 0.19 (1216.45 MB/s)
loading time = 0.25 (898.08 MB/s)

, что означает, что он может хранить очень быстро, т.е. узким местом обычно является диск.Однако, поскольку коэффициенты сжатия здесь довольно хорошие, эффективная скорость умножается на коэффициенты сжатия.Вот размеры этих 76 МБ массивов:

$ ll -h *.blp
-rw-r--r--  1 faltet  staff   921K Mar  6 13:50 a.blp
-rw-r--r--  1 faltet  staff   2.2M Mar  6 13:50 b.blp
-rw-r--r--  1 faltet  staff   1.4M Mar  6 13:50 c.blp

Обратите внимание, что использование компрессора Blosc является фундаментальным для достижения этой цели.Тот же сценарий, но с использованием 'clevel' = 0 (т.е. отключение сжатия):

$ python bench/store-blpk.py
store time = 3.36 (68.04 MB/s)
loading time = 2.61 (87.80 MB/s)

явно ограничивает производительность диска.

5 голосов
/ 27 марта 2014

Время поиска медленное, потому что при использовании mmap для не загружается содержимое массива в память при вызове метода load.Данные лениво загружаются, когда нужны конкретные данные.И это происходит при поиске в вашем случае.Но второй поиск не будет таким медленным.

Это хорошая особенность mmap, когда у вас большой массив, вам не нужно загружать целые данные в память.

Чтобы решить вашиВы можете использовать joblib . Вы можете записать любой объект, который хотите, используя joblib.dump, даже два или более numpy arrays, см. пример

firstArray = np.arange(100)
secondArray = np.arange(50)
# I will put two arrays in dictionary and save to one file
my_dict = {'first' : firstArray, 'second' : secondArray}
joblib.dump(my_dict, 'file_name.dat')
...