cách tốt nhất để bảo vệ các mảng không có hạt trên đĩa


124

Tôi đang tìm một cách nhanh chóng để lưu giữ các mảng lớn. Tôi muốn lưu chúng vào đĩa ở định dạng nhị phân, sau đó đọc lại chúng vào bộ nhớ tương đối nhanh. Rất tiếc, cPickle không đủ nhanh.

Tôi đã tìm thấy numpy.saveznumpy.load . Nhưng điều kỳ lạ là, numpy.load tải một tệp npy vào "memory-map". Điều đó có nghĩa là thao tác thường xuyên với các mảng thực sự chậm. Ví dụ, một cái gì đó như thế này sẽ thực sự chậm:

#!/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;

chính xác hơn, dòng đầu tiên sẽ thực sự nhanh, nhưng các dòng còn lại chỉ định các mảng objlại chậm một cách đáng kinh ngạc:

loading time =  0.000220775604248
assining time =  2.72940087318

Có cách nào tốt hơn để bảo quản mảng numpy không? Lý tưởng nhất là tôi muốn có thể lưu trữ nhiều mảng trong một tệp.


3
Theo mặc định, khôngnp.load nên mmap tệp.
Fred Foo

6
Điều gì về pytables ?
dsign

@larsmans, cảm ơn vì đã trả lời. nhưng tại sao thời gian tra cứu (z ['a'] trong ví dụ mã của tôi) quá chậm?
Vendetta

1
Sẽ rất tuyệt nếu chúng tôi có thêm một chút thông tin trong câu hỏi của bạn, chẳng hạn như loại mảng được lưu trữ trong ifile và kích thước của nó, hoặc nếu chúng là một số mảng trong các tệp khác nhau hoặc cách bạn lưu chúng một cách chính xác. Theo câu hỏi của bạn, tôi có ấn tượng rằng dòng đầu tiên không làm gì cả và việc tải thực tế xảy ra sau đó, nhưng đó chỉ là phỏng đoán.
dsign

19
@larsmans - Đối với những gì nó đáng giá, đối với một tệp "npz" (tức là nhiều mảng được lưu với numpy.savez), mặc định là "tải một cách lười biếng" các mảng. Nó không phải là ánh xạ chúng, nhưng nó không tải chúng cho đến khi NpzFileđối tượng được lập chỉ mục. (Như vậy sự chậm trễ OP là đề cập đến.) Các tài liệu cho loadbỏ qua này, và do đó là một liên lạc gây hiểu lầm ...
Joe Kington

Câu trả lời:


63

Tôi là một fan hâm mộ lớn của hdf5 để lưu trữ các mảng lớn. Có hai tùy chọn để xử lý hdf5 trong python:

http://www.pytables.org/

http://www.h5py.org/

Cả hai đều được thiết kế để làm việc với các mảng numpy một cách hiệu quả.


35
bạn có sẵn sàng cung cấp một số mã ví dụ bằng cách sử dụng các gói này để lưu một mảng không?
dbliss


1
Theo kinh nghiệm của tôi, hdf5 có hiệu suất đọc và ghi rất chậm khi kích hoạt tính năng nén và lưu trữ chunk. Ví dụ: tôi có hai mảng 2-D có hình dạng (2500,000 * 2000) với kích thước chunk (10,000 * 2000). Một thao tác ghi một mảng có hình dạng (2000 * 2000) sẽ mất khoảng 1 ~ 2 giây để hoàn thành. Bạn có đề xuất nào về việc cải thiện hiệu suất không? cám ơn.
Simon. Li

206

Tôi đã so sánh hiệu suất (không gian và thời gian) cho một số cách để lưu trữ mảng numpy. Rất ít trong số chúng hỗ trợ nhiều mảng trên mỗi tệp, nhưng có lẽ nó vẫn hữu ích.

điểm chuẩn cho lưu trữ mảng numpy

Các tệp Npy và nhị phân đều thực sự nhanh và nhỏ đối với dữ liệu dày đặc. Nếu dữ liệu thưa thớt hoặc rất có cấu trúc, bạn có thể muốn sử dụng npz với tính năng nén, điều này sẽ tiết kiệm rất nhiều dung lượng nhưng tốn một thời gian tải.

Nếu tính di động là một vấn đề, thì nhị phân tốt hơn npy. Nếu khả năng đọc của con người là quan trọng, thì bạn sẽ phải hy sinh rất nhiều hiệu suất, nhưng nó có thể đạt được khá tốt bằng cách sử dụng csv (tất nhiên cũng rất dễ di chuyển).

Thêm chi tiết và mã có sẵn tại github repo .


2
Bạn có thể giải thích tại sao binarytốt hơn là npytính di động? Điều này cũng áp dụng cho npz?
daniel451

1
@ daniel451 Bởi vì bất kỳ ngôn ngữ nào cũng có thể đọc tệp nhị phân nếu chúng chỉ biết hình dạng, kiểu dữ liệu và cho dù nó dựa trên hàng hay cột. Nếu bạn chỉ sử dụng Python thì npy cũng được, có lẽ dễ hơn một chút so với nhị phân.
Đánh dấu

1
Cảm ơn bạn! Một câu hỏi nữa: tôi có bỏ qua điều gì đó hay bạn đã bỏ qua HDF5? Vì điều này khá phổ biến, tôi sẽ quan tâm xem nó như thế nào so với các phương pháp khác.
daniel451

1
Tôi đã cố gắng sử dụng png và npy để lưu cùng một hình ảnh. png chỉ chiếm 2K không gian trong khi npy chiếm 307K. Kết quả này thực sự khác với công việc của bạn. Tôi có làm điều gì sai? Hình ảnh này là hình ảnh thang độ xám và chỉ có 0 và 255 ở bên trong. Tôi nghĩ rằng đây là một dữ liệu thưa thớt đúng không? Sau đó, tôi cũng sử dụng npz nhưng kích thước là hoàn toàn giống nhau.
York Yang

3
Tại sao h5py bị thiếu? Hay tôi đang thiếu cái gì đó?
daniel451

49

Hiện có một bản sao dựa trên HDF5 của pickleđược gọi 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 )

BIÊN TẬP:

Cũng có khả năng "chọn" trực tiếp vào một kho lưu trữ nén bằng cách thực hiện:

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' ) )

nén


ruột thừa

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' )

một cảnh báo mà một số ppl có thể quan tâm là pickle có thể thực thi mã tùy ý khiến nó kém an toàn hơn các giao thức lưu dữ liệu khác.
Charlie Parker

Điều đó thật tuyệt! Bạn cũng có thể cung cấp mã để đọc các tệp được tải trực tiếp vào nén bằng lzma hoặc bz2?
Ernest S Kirubakaran

14

savez () lưu dữ liệu vào tệp zip, Có thể mất một chút thời gian để nén và giải nén tệp. Bạn có thể sử dụng hàm 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()

Để lưu nhiều mảng trong một tệp, bạn chỉ cần mở tệp trước, sau đó lưu hoặc tải các mảng theo trình tự.


7

Một khả năng khác để lưu trữ mảng numpy một cách hiệu quả là 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)

và đầu ra cho máy tính xách tay của tôi (một chiếc MacBook Air tương đối cũ với bộ xử lý Core2):

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

điều đó có nghĩa là nó có thể lưu trữ rất nhanh, tức là nút cổ chai thường là đĩa. Tuy nhiên, vì tỷ lệ nén ở đây khá tốt nên tốc độ hiệu quả được nhân với tỷ lệ nén. Dưới đây là kích thước cho các mảng 76 MB này:

$ 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

Xin lưu ý rằng việc sử dụng máy nén Blosc là cơ bản để đạt được điều này. Cùng một tập lệnh nhưng sử dụng 'clevel' = 0 (tức là tắt tính năng nén):

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

rõ ràng là bị tắc nghẽn bởi hiệu suất đĩa.


2
Người mà nó có thể quan tâm: Mặc dù Bloscpack và PyTables là các dự án khác nhau, nhưng dự án trước đây chỉ tập trung vào kết xuất đĩa chứ không phải cắt các mảng được lưu trữ, tôi đã thử nghiệm cả hai và đối với "dự án kết xuất tệp" thuần túy, Bloscpack nhanh hơn PyTables gần 6 lần.
Marcelo Sardelich

4

Thời gian tra cứu chậm vì khi bạn sử dụng mmapkhông tải nội dung của mảng vào bộ nhớ khi bạn gọiload phương thức. Dữ liệu được tải chậm khi cần dữ liệu cụ thể. Và điều này xảy ra khi tra cứu trong trường hợp của bạn. Nhưng lần tra cứu thứ hai sẽ không quá chậm.

Đây là một tính năng tuyệt vời mmapkhi bạn có một mảng lớn, bạn không phải tải toàn bộ dữ liệu vào bộ nhớ.

Để giải quyết bạn có thể sử dụng joblib, bạn có thể kết xuất bất kỳ đối tượng nào bạn muốn bằng cách sử dụng joblib.dumpthậm chí hai hoặc nhiều hơn numpy arrays, hãy xem ví dụ

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')

Thư viện không còn nữa.
Andrea Moro
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.