HDF5 Avantajları: Organizasyon, esneklik, birlikte çalışabilirlik
HDF5'in ana avantajlarından bazıları, hiyerarşik yapısı (klasörlere / dosyalara benzer), her bir öğeyle birlikte depolanan isteğe bağlı isteğe bağlı meta veriler ve esnekliğidir (örn. Sıkıştırma). Bu organizasyon yapısı ve meta veri depolama kulağa önemsiz gelebilir, ancak pratikte çok kullanışlıdır.
HDF'nin diğer bir avantajı, veri kümelerinin sabit boyutlu veya esnek boyutta olabilmesidir . Bu nedenle, tamamen yeni bir kopya oluşturmak zorunda kalmadan verileri büyük bir veri kümesine eklemek kolaydır.
Ek olarak, HDF5 hemen hemen her dil için mevcut kitaplıklara sahip standartlaştırılmış bir formattır, bu nedenle diskteki verilerinizi örneğin Matlab, Fortran, R, C ve Python arasında paylaşmak HDF ile çok kolaydır. (Dürüst olmak gerekirse, C'ye karşı F sıralamasının farkında olduğunuz ve depolanan dizinin şeklini, tipini vb. Bildiğiniz sürece, büyük bir ikili dizi için de çok zor değildir.)
Büyük bir dizi için HDF avantajları: Rasgele bir dilim için daha hızlı G / Ç
Sadece TL / DR olarak: Bir ~ 8GB 3D dizisi için, herhangi bir eksen boyunca bir "tam" dilim okuma HDF5 veri kümesini chunked bir ile ~ 20 saniye sürdü ve 0.3 saniye (en iyi durum) için üç saatten fazla (en kötü durum) için aynı verilerin mem eşlenmiş bir dizisi.
Yukarıda listelenenlerin ötesinde, HDF5 gibi "yığınlanmış" * bir disk üstü veri formatının bir başka büyük avantajı daha var: Rasgele bir dilimi okumak (isteğe bağlı olarak vurgulanır), diskteki veriler daha bitişik olduğundan, tipik olarak çok daha hızlı olacaktır. ortalama.
*
(HDF5'in yığınlanmış bir veri biçimi olması gerekmez. Parçalamayı destekler, ancak gerektirmez. Aslında, bir veri kümesi oluşturmanın varsayılanı h5py
, doğru hatırlıyorsam yığın yapmak değildir.)
Temel olarak, en iyi durum disk okuma hızınız ve veri kümenizin belirli bir bölümü için en kötü durumdaki disk okuma hızınız, yığınlanmış bir HDF veri kümesine oldukça yakın olacaktır (makul bir yığın boyutu seçtiğinizi veya bir kitaplığın sizin için bir tane seçmesine izin verdiğinizi varsayarak). Basit bir ikili dizi ile en iyi durum daha hızlıdır, ancak en kötü durum çok daha kötüdür.
Bir uyarı, bir SSD'niz varsa, okuma / yazma hızında büyük bir fark görmeyeceksiniz. Normal bir sabit diskle, sıralı okumalar rastgele okumalardan çok çok daha hızlıdır. (ör. Normal bir sabit diskin uzun seek
süresi vardır.) HDF'nin hala bir SSD üzerinde avantajı vardır, ancak ham hızdan çok diğer özelliklerinden (örn. meta veri, organizasyon, vb.) kaynaklanmaktadır.
İlk olarak, kafa karışıklığını gidermek için, bir h5py
veri kümesine erişmek, uyuşuk bir diziye oldukça benzer şekilde davranan, ancak dilimlenene kadar verileri belleğe yüklemeyen bir nesne döndürür. (Memmap'e benzer, ancak aynı değildir.) Daha fazla bilgi için h5py
giriş kısmına bir göz atın .
Veri kümesini dilimlemek, verilerin bir alt kümesini belleğe yükleyecektir, ancak muhtemelen onunla bir şeyler yapmak istersiniz, bu noktada yine de bellekte ihtiyacınız olacak.
Çekirdek dışı hesaplamalar yapmak istiyorsanız, pandas
veya ile tablo verileri için oldukça kolay bir şekilde yapabilirsiniz pytables
. h5py
(Büyük ND dizileri için daha güzel) ile mümkündür , ancak daha düşük bir seviyeye inmeniz ve yinelemeyi kendiniz halletmeniz gerekir.
Bununla birlikte, uyuşuk gibi çekirdek dışı hesaplamaların geleceği Blaze'dir. Bu rotayı gerçekten kullanmak istiyorsanız bir göz atın .
"Parçalanmamış" durum
Öncelikle, diske yazılan 3B C sıralı bir dizi düşünün ( arr.ravel()
her şeyi daha görünür kılmak için sonucu çağırıp yazdırarak simüle edeceğim ):
In [1]: import numpy as np
In [2]: arr = np.arange(4*6*6).reshape(4,6,6)
In [3]: arr
Out[3]:
array([[[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[ 12, 13, 14, 15, 16, 17],
[ 18, 19, 20, 21, 22, 23],
[ 24, 25, 26, 27, 28, 29],
[ 30, 31, 32, 33, 34, 35]],
[[ 36, 37, 38, 39, 40, 41],
[ 42, 43, 44, 45, 46, 47],
[ 48, 49, 50, 51, 52, 53],
[ 54, 55, 56, 57, 58, 59],
[ 60, 61, 62, 63, 64, 65],
[ 66, 67, 68, 69, 70, 71]],
[[ 72, 73, 74, 75, 76, 77],
[ 78, 79, 80, 81, 82, 83],
[ 84, 85, 86, 87, 88, 89],
[ 90, 91, 92, 93, 94, 95],
[ 96, 97, 98, 99, 100, 101],
[102, 103, 104, 105, 106, 107]],
[[108, 109, 110, 111, 112, 113],
[114, 115, 116, 117, 118, 119],
[120, 121, 122, 123, 124, 125],
[126, 127, 128, 129, 130, 131],
[132, 133, 134, 135, 136, 137],
[138, 139, 140, 141, 142, 143]]])
Değerler, aşağıdaki 4. satırda gösterildiği gibi sıralı olarak diskte saklanacaktır. (Şu an için dosya sistemi ayrıntılarını ve parçalanmayı görmezden gelelim.)
In [4]: arr.ravel(order='C')
Out[4]:
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143])
En iyi senaryoda, birinci eksen boyunca bir dilim alalım. Bunların dizinin ilk 36 değeri olduğuna dikkat edin. Bu çok hızlı bir okuma olacak! (bir arama, bir okuma)
In [5]: arr[0,:,:]
Out[5]:
array([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35]])
Benzer şekilde, ilk eksendeki sonraki dilim sadece sonraki 36 değer olacaktır. Bu eksen boyunca tam bir dilimi okumak için sadece bir seek
işleme ihtiyacımız var . Tüm okuyacağımız bu eksen boyunca çeşitli dilimlerse, o zaman bu mükemmel dosya yapısıdır.
Ancak en kötü durum senaryosunu ele alalım: Son eksen boyunca bir dilim.
In [6]: arr[:,:,0]
Out[6]:
array([[ 0, 6, 12, 18, 24, 30],
[ 36, 42, 48, 54, 60, 66],
[ 72, 78, 84, 90, 96, 102],
[108, 114, 120, 126, 132, 138]])
Bu dilimi okumak için, tüm değerler diskte ayrıldığından 36 arama ve 36 okumaya ihtiyacımız var. Hiçbiri bitişik değil!
Bu oldukça küçük görünebilir, ancak daha büyük ve daha büyük dizilere seek
geçtikçe , işlemlerin sayısı ve boyutu hızla büyüyor. Bu şekilde depolanan ve üzerinden okunan büyük boyutlu (~ 10Gb) bir 3B dizi memmap
için, "en kötü" eksen boyunca tam bir dilimi okumak, modern donanımlarla bile kolayca onlarca dakika alabilir. Aynı zamanda, en iyi eksen boyunca bir dilim bir saniyeden daha kısa sürebilir. Basit olması için, sadece tek bir eksen boyunca "tam" dilimler gösteriyorum, ancak aynı şey herhangi bir veri alt kümesinin rastgele dilimlerinde de oluyor.
Bu arada, bundan yararlanan ve temelde büyük 3B dizilerin üç kopyasını diskte depolayan birkaç dosya formatı vardır : biri C sırasına, biri F sırasına ve diğeri ikisi arasındaki ara parçaya. (Bunun bir örneği Geoprobe'un D3D formatıdır, ancak herhangi bir yerde belgelendiğinden emin değilim.) Son dosya boyutunun 4TB olması kimin umurunda, depolama ucuz! Bunun çılgın yanı, ana kullanım durumu her yönde tek bir alt dilim çıkarmak olduğu için yapmak istediğiniz okumaların çok çok hızlı olmasıdır. Çok iyi çalışıyor!
Basit "parçalanmış" durum
Diyelim ki 3B dizinin 2x2x2 "parçalarını" diskte bitişik bloklar olarak depoluyoruz. Başka bir deyişle, şöyle bir şey:
nx, ny, nz = arr.shape
slices = []
for i in range(0, nx, 2):
for j in range(0, ny, 2):
for k in range(0, nz, 2):
slices.append((slice(i, i+2), slice(j, j+2), slice(k, k+2)))
chunked = np.hstack([arr[chunk].ravel() for chunk in slices])
Böylece diskteki veriler şöyle görünür chunked
:
array([ 0, 1, 6, 7, 36, 37, 42, 43, 2, 3, 8, 9, 38,
39, 44, 45, 4, 5, 10, 11, 40, 41, 46, 47, 12, 13,
18, 19, 48, 49, 54, 55, 14, 15, 20, 21, 50, 51, 56,
57, 16, 17, 22, 23, 52, 53, 58, 59, 24, 25, 30, 31,
60, 61, 66, 67, 26, 27, 32, 33, 62, 63, 68, 69, 28,
29, 34, 35, 64, 65, 70, 71, 72, 73, 78, 79, 108, 109,
114, 115, 74, 75, 80, 81, 110, 111, 116, 117, 76, 77, 82,
83, 112, 113, 118, 119, 84, 85, 90, 91, 120, 121, 126, 127,
86, 87, 92, 93, 122, 123, 128, 129, 88, 89, 94, 95, 124,
125, 130, 131, 96, 97, 102, 103, 132, 133, 138, 139, 98, 99,
104, 105, 134, 135, 140, 141, 100, 101, 106, 107, 136, 137, 142, 143])
Ve sadece 2x2x2 blok olduklarını göstermek için, bunların arr
ilk 8 değeri olduğuna dikkat edin chunked
:
In [9]: arr[:2, :2, :2]
Out[9]:
array([[[ 0, 1],
[ 6, 7]],
[[36, 37],
[42, 43]]])
Bir eksen boyunca herhangi bir dilimde okumak için, ya 6 ya da 9 bitişik parça halinde (ihtiyacımızın iki katı kadar veri) okurduk ve sonra sadece istediğimiz kısmı tutardık. Bu, en kötü durum maksimum 9 arama ve yığınlanmamış sürüm için maksimum 36 arama. (Ama en iyi durum, memmapped dizi için hala 6 arama vs 1'dir.) Sıralı okumalar, aramalara kıyasla çok hızlı olduğundan, bu, keyfi bir alt kümeyi belleğe okumak için gereken süreyi önemli ölçüde azaltır. Bir kez daha, bu etki daha büyük dizilerle daha da büyüyor.
HDF5 bunu birkaç adım daha ileri götürür. Parçaların bitişik olarak depolanması gerekmez ve bir B-Ağacı tarafından indekslenirler. Ayrıca, diskte aynı boyutta olmaları gerekmez, bu nedenle her bir parçaya sıkıştırma uygulanabilir.
Parçalanmış diziler h5py
Varsayılan olarak, h5py
diskte yığınlanmış HDF dosyaları oluşturmaz ( pytables
tersine yaptığını düşünüyorum ). chunks=True
Bununla birlikte, veri kümesini oluştururken belirtirseniz , diskte yığın halinde bir dizi elde edersiniz.
Hızlı, minimal bir örnek olarak:
import numpy as np
import h5py
data = np.random.random((100, 100, 100))
with h5py.File('test.hdf', 'w') as outfile:
dset = outfile.create_dataset('a_descriptive_name', data=data, chunks=True)
dset.attrs['some key'] = 'Did you want some metadata?'
Bunun bizim için otomatik olarak bir yığın boyutu seçmemizi chunks=True
söylediğini unutmayın h5py
. En yaygın kullanım durumunuz hakkında daha fazla şey biliyorsanız, bir şekil demeti belirleyerek (örneğin (2,2,2)
yukarıdaki basit örnekte) yığın boyutunu / şeklini optimize edebilirsiniz . Bu, belirli bir eksen boyunca okumaları daha verimli hale getirmenize veya belirli bir boyuttaki okumalar / yazmalar için optimize etmenize olanak tanır.
I / O Performans karşılaştırması
Sadece noktayı vurgulamak için, yığınlanmış bir HDF5 veri kümesinden dilimler halinde okumayı ve aynı kesin verileri içeren büyük (~ 8 GB), Fortran sıralı bir 3B diziyi karşılaştıralım.
Ben ettik tüm OS önbelleklerini temizlenir biz "soğuk" bir performans elde ediyor, böylece her çalışma arasında.
Her dosya türü için, birinci eksen boyunca "tam" bir x diliminde ve son eksen boyunca "tam" bir z diliminde okumayı test edeceğiz. Fortran sıralı memmapped dizisi için, "x" dilimi en kötü durumdur ve "z" dilimi en iyi durumdur.
Kullanılan kod bir özet içindedir ( hdf
dosyanın oluşturulması dahil ). Burada kullanılan verileri kolayca paylaşamıyorum, ancak aynı şekle ( 621, 4991, 2600)
ve türüne sahip bir dizi sıfırla) simüle edebilirsiniz np.uint8
.
chunked_hdf.py
Böyle görünüyor:
import sys
import h5py
def main():
data = read()
if sys.argv[1] == 'x':
x_slice(data)
elif sys.argv[1] == 'z':
z_slice(data)
def read():
f = h5py.File('/tmp/test.hdf5', 'r')
return f['seismic_volume']
def z_slice(data):
return data[:,:,0]
def x_slice(data):
return data[0,:,:]
main()
memmapped_array.py
benzerdir, ancak dilimlerin gerçekten belleğe yüklenmesini sağlamak için daha karmaşık bir dokunuşa sahiptir (varsayılan olarak, başka bir memmapped
dizi döndürülür, bu da elmalarla elmalar karşılaştırması olmaz).
import numpy as np
import sys
def main():
data = read()
if sys.argv[1] == 'x':
x_slice(data)
elif sys.argv[1] == 'z':
z_slice(data)
def read():
big_binary_filename = '/data/nankai/data/Volumes/kumdep01_flipY.3dv.vol'
shape = 621, 4991, 2600
header_len = 3072
data = np.memmap(filename=big_binary_filename, mode='r', offset=header_len,
order='F', shape=shape, dtype=np.uint8)
return data
def z_slice(data):
dat = np.empty(data.shape[:2], dtype=data.dtype)
dat[:] = data[:,:,0]
return dat
def x_slice(data):
dat = np.empty(data.shape[1:], dtype=data.dtype)
dat[:] = data[0,:,:]
return dat
main()
Önce HDF performansına bir göz atalım:
jofer at cornbread in ~
$ sudo ./clear_cache.sh
jofer at cornbread in ~
$ time python chunked_hdf.py z
python chunked_hdf.py z 0.64s user 0.28s system 3% cpu 23.800 total
jofer at cornbread in ~
$ sudo ./clear_cache.sh
jofer at cornbread in ~
$ time python chunked_hdf.py x
python chunked_hdf.py x 0.12s user 0.30s system 1% cpu 21.856 total
"Tam" bir x dilimi ve "tam" bir z dilimi yaklaşık olarak aynı süreyi alır (~ 20 saniye). Bunun 8GB'lık bir dizi olduğunu düşünürsek, bu çok da kötü değil. Çoğu zaman
Ve bunu memmapped dizi zamanlarıyla karşılaştırırsak (Fortran sıralı: "z-dilim" en iyi durum ve "x-dilim" en kötü durumdur.):
jofer at cornbread in ~
$ sudo ./clear_cache.sh
jofer at cornbread in ~
$ time python memmapped_array.py z
python memmapped_array.py z 0.07s user 0.04s system 28% cpu 0.385 total
jofer at cornbread in ~
$ sudo ./clear_cache.sh
jofer at cornbread in ~
$ time python memmapped_array.py x
python memmapped_array.py x 2.46s user 37.24s system 0% cpu 3:35:26.85 total
Evet, doğru okudunuz. Bir dilim yönü için 0,3 saniye ve diğeri için ~ 3,5 saat .
"X" yönünde dilimleme süresi, tüm 8GB diziyi belleğe yüklemek ve istediğimiz dilimi seçmek için gereken süreden çok daha uzundur! (Yine, bu Fortran sıralı bir dizidir. Ters x / z dilim zamanlaması, C sıralı bir dizi için geçerli olacaktır.)
Bununla birlikte, her zaman en iyi durum yönünde bir dilim almak istiyorsak, diskteki büyük ikili dizi çok iyidir. (~ 0.3 saniye!)
Memmaplı bir dizide, bu G / Ç tutarsızlığına takılı kalırsınız (veya belki anizotropi daha iyi bir terimdir). Bununla birlikte, yığınlanmış bir HDF veri kümesiyle, erişimin eşit olacağı veya belirli bir kullanım durumu için optimize edileceği şekilde yığın boyutunu seçebilirsiniz. Size çok daha fazla esneklik sağlar.
Özetle
Umarım bu, sorunuzun bir bölümünü her halükarda çözmenize yardımcı olur. HDF5'in "ham" memmap'lere göre birçok başka avantajı var, ancak burada hepsini genişletmek için yerim yok. Sıkıştırma bazı şeyleri hızlandırabilir (çalıştığım veriler sıkıştırmadan fazla fayda sağlamaz, bu yüzden nadiren kullanırım) ve işletim sistemi düzeyinde önbelleğe alma genellikle HDF5 dosyalarında "ham" memmap'lardan daha güzel bir şekilde yürütülür. Bunun ötesinde, HDF5 gerçekten harika bir kapsayıcı formatıdır. Verilerinizi yönetmede size büyük bir esneklik sağlar ve az çok herhangi bir programlama dilinden kullanılabilir.
Genel olarak, deneyin ve kullanım durumunuz için iyi çalışıp çalışmadığını görün. Sanırım şaşırabilirsin.
h5py
sizinki gibi veri kümelerine göre daha uygundurpytables
. Ayrıca,h5py
etmez olmayan bir bellek numpy dizisi döndürür. Bunun yerine, tek gibi davranan, ancak belleğe yüklenmeyen (birmemmapped
diziye benzer ) bir şey döndürür . Daha eksiksiz bir cevap yazıyorum (bitiremeyebilir), ama umarım bu yorum bu arada biraz yardımcı olur.