HDF5 लाभ: संगठन, लचीलापन, अंतर
एचडीएफ 5 के कुछ मुख्य लाभ इसकी पदानुक्रमित संरचना (फ़ोल्डर्स / फ़ाइलों के समान), प्रत्येक आइटम के साथ संग्रहीत वैकल्पिक मनमाना मेटाडेटा, और इसके लचीलेपन (जैसे संपीड़न) हैं। यह संगठनात्मक संरचना और मेटाडेटा भंडारण तुच्छ लग सकता है, लेकिन व्यवहार में यह बहुत उपयोगी है।
HDF का एक और फायदा यह है कि डेटासेट निश्चित आकार या लचीले आकार के हो सकते हैं। इसलिए, पूरी नई प्रति बनाने के बिना बड़े डेटासेट में डेटा जोड़ना आसान है।
इसके अतिरिक्त, HDF5 लगभग किसी भी भाषा के लिए उपलब्ध पुस्तकालयों के साथ एक मानकीकृत प्रारूप है, इसलिए मतलाब, फोरट्रान, आर, सी और पायथन के बीच अपने ऑन-डिस्क डेटा को साझा करना, एचडीएफ के साथ बहुत आसान है। (निष्पक्ष होने के लिए, यह एक बड़ी द्विआधारी सरणी के साथ बहुत मुश्किल नहीं है, भी, जब तक कि आप सी बनाम एफ ऑर्डर के बारे में जानते हैं और संग्रहीत सरणी के आकार, प्रारूप, आदि को जानते हैं।)
एक बड़े सरणी के लिए एचडीएफ लाभ: एक मनमाना टुकड़ा का तेज़ I / O
टीएल / डीआर के रूप में: ~ 8 जीबी 3 डी सरणी के लिए, किसी भी धुरी पर "पूर्ण" स्लाइस को पढ़ते हुए ~ 20 सेकंड के लिए एक घुटे हुए एचडीएफ 5 डेटासेट के साथ, और 0.3 सेकंड (सर्वश्रेष्ठ-केस) से तीन घंटे (सबसे खराब स्थिति) के लिए एक ही डेटा का एक याद किया सरणी।
ऊपर सूचीबद्ध चीज़ों से परे, "चंक्ड" * ऑन-डिस्क डेटा प्रारूप जैसे HDF5 का एक और बड़ा फायदा है: एक मनमाना टुकड़ा (मनमाना जोर देना) पढ़ना आमतौर पर बहुत तेज़ होगा, क्योंकि ऑन-डिस्क डेटा अधिक उपयोगी है औसत।
*
(HDF5 को एक चैंकाने वाला डेटा फॉर्मेट होना जरूरी नहीं है। यह चैंकिंग का समर्थन करता है, लेकिन इसकी आवश्यकता नहीं है। वास्तव में, डेटासेट बनाने के लिए डिफ़ॉल्ट h5py
सही नहीं है, अगर मुझे सही तरीके से याद है।)
मूल रूप से, आपकी सबसे अच्छी स्थिति डिस्क-रीड स्पीड और आपके डेटासेट के दिए गए स्लाइस के लिए आपकी सबसे खराब स्थिति डिस्क रीड स्पीड एक chunked HDF डेटासेट के साथ काफी करीब होगी (यह मानते हुए कि आपने एक उचित चंक आकार चुना है या लाइब्रेरी को आपके लिए एक चुनने दें)। एक साधारण बाइनरी सरणी के साथ, सबसे अच्छा मामला तेज है, लेकिन सबसे खराब स्थिति बहुत खराब है।
एक चेतावनी, यदि आपके पास SSD है, तो आपको पढ़ने / लिखने की गति में बहुत बड़ा अंतर नजर नहीं आएगा। एक नियमित हार्ड ड्राइव के साथ, हालांकि, अनुक्रमिक रीड्स बहुत अधिक होते हैं, यादृच्छिक रीड की तुलना में बहुत तेज होते हैं। (यानी एक नियमित हार्ड ड्राइव में लंबा seek
समय होता है।) HDF का अभी भी SSD पर एक फायदा है, लेकिन यह इसकी अन्य विशेषताओं (जैसे मेटाडेटा, संगठन, आदि) के कारण कच्ची गति से अधिक है।
सबसे पहले, भ्रम को दूर करने के लिए, h5py
डेटासेट एक्सेस करने से एक ऑब्जेक्ट वापस आ जाता है जो काफी हद तक एक समान सरणी के साथ व्यवहार करता है, लेकिन डेटा को मेमोरी में लोड नहीं करता है जब तक कि यह कटा हुआ नहीं हो। (मेममैप के समान, लेकिन समान नहीं।) अधिक जानकारी के लिए h5py
परिचय पर एक नज़र डालें ।
डेटासेट का स्लाइसिंग करने से डेटा का एक सबसेट मेमोरी में लोड हो जाएगा, लेकिन संभवत: आप इसके साथ कुछ करना चाहते हैं, जिस बिंदु पर आपको किसी भी तरह से मेमोरी की आवश्यकता होगी।
यदि आप आउट-ऑफ-कोर संगणना करना चाहते हैं, तो आप के साथ pandas
या सारणीबद्ध डेटा के लिए काफी आसानी से कर सकते हैं pytables
। यह h5py
(बड़े एनडी सरणियों के लिए अच्छे) के साथ संभव है , लेकिन आपको एक स्पर्श निचले स्तर तक छोड़ने और अपने आप को पुनरावृत्ति को संभालने की आवश्यकता है।
हालांकि, भविष्य के प्रमुख-संगणना-योग्य संगणनाओं का भविष्य ब्लेज़ है। यदि आप वास्तव में उस मार्ग को लेना चाहते हैं तो इस पर एक नज़र डालें ।
"अनसंकटेड" मामला
सबसे पहले, डिस्क पर लिखे गए 3 डी सी-ऑर्डर किए गए सरणी पर विचार करें (मैं arr.ravel()
परिणाम को कॉल करके और प्रिंट करके, चीजों को अधिक दृश्यमान बनाने के लिए अनुकरण करूंगा ):
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]]])
मानों को डिस्क पर क्रमिक रूप से संग्रहीत किया जाएगा जैसा कि नीचे पंक्ति 4 पर दिखाया गया है। (आइए फिलहाल फाइल सिस्टम विवरण और विखंडन की उपेक्षा करें।)
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])
सबसे अच्छी स्थिति में, आइए पहले धुरी पर एक स्लाइस लें। ध्यान दें कि ये सरणी के पहले 36 मान हैं। यह बहुत तेजी से पढ़ा जाएगा! (एक तलाश, एक पढ़ा)
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]])
इसी तरह, पहली धुरी के साथ अगला टुकड़ा सिर्फ अगले 36 मान होगा। इस अक्ष के साथ एक पूरा टुकड़ा पढ़ने के लिए, हमें केवल एक seek
ऑपरेशन की आवश्यकता है। यदि हम सभी पढ़ने जा रहे हैं तो इस अक्ष के साथ विभिन्न स्लाइस हैं, तो यह सही फ़ाइल संरचना है।
हालांकि, चलो सबसे खराब स्थिति पर विचार करते हैं: अंतिम अक्ष के साथ एक टुकड़ा।
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]])
इस स्लाइस को पढ़ने के लिए, हमें 36 और 36 रीड की जरूरत है, क्योंकि सभी मान डिस्क पर अलग हो जाते हैं। उनमें से कोई भी आसन्न नहीं है!
यह बहुत मामूली लग सकता है, लेकिन जैसा कि हम बड़े और बड़े सरणियों में आते हैं, seek
संचालन की संख्या और आकार तेजी से बढ़ता है। इस तरह से संग्रहित एक बड़े-ईश (~ 10Gb) 3D सरणी memmap
के लिए और "सबसे खराब" अक्ष के साथ एक पूर्ण स्लाइस को पढ़ते हुए, आधुनिक हार्डवेयर के साथ भी आसानी से दसियों मिनट ले सकते हैं। इसी समय, सबसे अच्छी धुरी के साथ एक टुकड़ा एक सेकंड से भी कम समय ले सकता है। सादगी के लिए, मैं केवल एक अक्ष के साथ "पूर्ण" स्लाइस दिखा रहा हूं, लेकिन सटीक वही होता है जो डेटा के किसी सबसेट के मनमाने स्लाइस के साथ होता है।
संयोग से कई फ़ाइल प्रारूप हैं जो इसका लाभ उठाते हैं और मूल रूप से डिस्क पर विशाल 3 डी सरणियों की तीन प्रतियां संग्रहीत करते हैं : सी-ऑर्डर में एक, एफ-ऑर्डर में एक और दोनों के बीच एक में। (इसका एक उदाहरण जियोप्राइव का डी 3 डी प्रारूप है, हालांकि मुझे यकीन नहीं है कि यह कहीं भी प्रलेखित नहीं है।) अगर अंतिम फ़ाइल का आकार 4TB है, तो भंडारण कौन सस्ता है! इसके बारे में पागल बात यह है कि क्योंकि मुख्य उपयोग का मामला प्रत्येक दिशा में एक एकल उप-स्लाइस निकाल रहा है, आप जो पढ़ना चाहते हैं वह बहुत तेज़ है। ये अच्छी तरह काम करता है!
सरल "chunked" मामला
मान लें कि हम डिस्क पर सन्निहित ब्लॉक के रूप में 3 डी सरणी के 2x2x2 "चंक्स" को संग्रहीत करते हैं। दूसरे शब्दों में, कुछ इस तरह:
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])
तो डिस्क पर डेटा की तरह दिखेगा 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])
और सिर्फ यह दिखाने के लिए कि वे 2x2x2 ब्लॉक हैं arr
, ध्यान दें कि ये पहले 8 मान हैंchunked
:
In [9]: arr[:2, :2, :2]
Out[9]:
array([[[ 0, 1],
[ 6, 7]],
[[36, 37],
[42, 43]]])
एक धुरी के साथ किसी भी स्लाइस में पढ़ने के लिए, हम 6 या 9 सन्निकट विखंडू में पढ़ेंगे (जितना आवश्यक हो उतना ही दोगुना डेटा) और फिर केवल उस हिस्से को रखें जो हम चाहते थे। यह एक सबसे खराब स्थिति है, 9 गैर-chunked संस्करण के लिए अधिकतम 36 का प्रयास करता है। (लेकिन सबसे अच्छा मामला अभी भी याद किए गए एरे के लिए 6 बनाम 1 है।) क्योंकि अनुक्रमिक रीड्स, वॉच की तुलना में बहुत तेज हैं, यह स्मृति में एक मनमाना उपसमूह को पढ़ने में लगने वाले समय को काफी कम कर देता है। एक बार फिर, यह प्रभाव बड़े सरणियों के साथ बड़ा हो जाता है।
HDF5 इसे कुछ कदम आगे ले जाता है। विखंडू को संचित रूप से संग्रहीत नहीं करना पड़ता है, और वे बी-ट्री द्वारा अनुक्रमित होते हैं। इसके अलावा, उन्हें डिस्क पर एक ही आकार का होना जरूरी नहीं है, इसलिए प्रत्येक चंक पर संपीड़न लागू किया जा सकता है।
के साथ मंथन किया h5py
डिफ़ॉल्ट रूप से, h5py
डिस्क पर chunked HDF फ़ाइलें नहीं बनाई गई हैं (मुझे लगता pytables
है कि , इसके विपरीत)। यदि आप chunks=True
डाटासेट बनाते समय निर्दिष्ट करते हैं, हालांकि, आपको डिस्क पर एक chunked सरणी मिलेगी।
एक त्वरित, न्यूनतम उदाहरण के रूप में:
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?'
ध्यान दें कि स्वचालित रूप से हमारे लिए एक चंक आकार चुनने के लिए chunks=True
कहता h5py
है। यदि आप अपने सबसे सामान्य उपयोग-मामले के बारे में अधिक जानते हैं, तो आप आकार आकार (जैसे (2,2,2)
ऊपर सरल उदाहरण में) निर्दिष्ट करके चंक आकार / आकार को अनुकूलित कर सकते हैं । यह आपको एक विशेष अक्ष के साथ रीड्स को अधिक कुशल बनाने या किसी निश्चित आकार के रीड / राइट के लिए अनुकूलन करने की अनुमति देता है।
I / O प्रदर्शन तुलना
बस इस बिंदु पर जोर देने के लिए, आइए एक कटा हुआ HDF5 डेटासेट और एक बड़े (~ 8 जीबी) स्लाइस में पढ़ने की तुलना करें, फोरट्रान-आदेशित 3 डी सरणी जिसमें एक ही सटीक डेटा है।
मैंने प्रत्येक रन के बीच सभी ओएस कैश को साफ कर दिया है , इसलिए हम "ठंडा" प्रदर्शन देख रहे हैं।
प्रत्येक फ़ाइल प्रकार के लिए, हम पहले अक्ष के साथ "पूर्ण" x- स्लाइस और अंतिम अक्ष के साथ "पूर्ण" z-slize में पढ़ने का परीक्षण करेंगे। फोरट्रान-द्वारा मेम किए गए सरणी के लिए, "x" स्लाइस सबसे खराब स्थिति है, और "z" स्लाइस सबसे अच्छा मामला है।
उपयोग किया गया कोड एक जिस्ट ( hdf
फाइल बनाने सहित ) में है। मैं यहां उपयोग किए गए डेटा को आसानी से साझा नहीं कर सकता, लेकिन आप इसे उसी आकार ( 621, 4991, 2600)
और प्रकार) के शून्य द्वारा अनुकरण कर सकते हैं np.uint8
।
इस chunked_hdf.py
तरह दिखता है:
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
समान है, लेकिन यह सुनिश्चित करने के लिए एक स्पर्श अधिक जटिलता है कि स्लाइस वास्तव में मेमोरी में लोड किए गए हैं (डिफ़ॉल्ट रूप से, एक और memmapped
सरणी वापस आ जाएगी, जो एक सेब से सेब की तुलना नहीं होगी)।
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()
पहले HDF प्रदर्शन पर एक नजर डालते हैं:
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
एक "पूर्ण" एक्स-स्लाइस और एक "पूर्ण" जेड-स्लाइस समय की एक ही राशि (~ 20 सेकंड) के बारे में लेते हैं। यह देखते हुए कि यह 8GB का सरणी है, यह बहुत बुरा नहीं है। सर्वाधिक समय
और अगर हम इसकी तुलना मेम्नेप्ड ऐरे बार से करते हैं (यह फोरट्रान-ऑर्डर किया गया है: "z-slice" सबसे अच्छा मामला है और "x-slice" सबसे खराब स्थिति है।):
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
हां, आपने उसे सही पढ़ा है। एक स्लाइस दिशा के लिए 0.3 सेकंड और दूसरे के लिए ~ 3.5 घंटे ।
"X" दिशा में टुकड़ा करने के लिए समय आ गया है अब तक अब समय की राशि यह स्मृति में पूरे 8GB सरणी लोड और टुकड़ा हम चाहते थे चयन करने के लिए ले जाएगा की तुलना में! (फिर से, यह एक फोरट्रान-ऑर्डर की गई सरणी है। विपरीत x / z टुकड़ा समय सी-ऑर्डर की गई सरणी के लिए मामला होगा।)
हालांकि, अगर हम हमेशा सर्वश्रेष्ठ-केस दिशा के साथ एक स्लाइस लेना चाहते हैं, तो डिस्क पर बड़ा बाइनरी सरणी बहुत अच्छा है। (~ 0.3 सेकंड!)
एक मेमरेप्ड सरणी के साथ, आप इस I / O विसंगति (या शायद aisotropy एक बेहतर शब्द है) के साथ फंस गए हैं। हालाँकि, एक chunked HDF डेटासेट के साथ, आप चुन सकते हैं जैसे कि एक्सेस समान है या किसी विशेष उपयोग-केस के लिए ऑप्टिमाइज़ किया गया है। यह आपको बहुत अधिक लचीलापन देता है।
संक्षेप में
उम्मीद है कि आपके प्रश्न के एक हिस्से को किसी भी दर पर स्पष्ट करने में मदद करता है। HDF5 के "कच्चे" मेमपैप पर कई अन्य फायदे हैं, लेकिन मेरे पास उन सभी पर विस्तार करने के लिए जगह नहीं है। संपीड़न कुछ चीजों को गति दे सकता है (मेरे साथ काम करने वाला डेटा संपीड़न से बहुत लाभ नहीं करता है, इसलिए मैं शायद ही कभी इसका उपयोग करता हूं), और ओएस-स्तरीय कैशिंग अक्सर "कच्चे" मेमैप्स की तुलना में एचडीएफ 5 फाइलों के साथ अधिक अच्छी तरह से खेलता है। इसके अलावा, एचडीएफ 5 वास्तव में शानदार कंटेनर प्रारूप है। यह आपको अपने डेटा को प्रबंधित करने में बहुत अधिक लचीलापन देता है, और इसे कम या ज्यादा किसी भी प्रोग्रामिंग भाषा से उपयोग किया जा सकता है।
कुल मिलाकर, इसे आज़माएँ और देखें कि क्या यह आपके उपयोग के मामले में अच्छा काम करता है। मुझे लगता है कि आप आश्चर्यचकित हो सकते हैं।
h5py
तुम्हारे जैसे डेटासेट के लिए बेहतर अनुकूल हैpytables
। साथ ही, इन-मेमोरी सुपीरियर एरे को वापस नहींh5py
करता है । इसके बजाय यह कुछ ऐसा करता है जो एक जैसा व्यवहार करता है, लेकिन मेमोरी में लोड नहीं होता है ( सरणी के समान )। मैं एक और अधिक पूर्ण उत्तर लिख रहा हूं (इसे समाप्त नहीं कर सकता), लेकिन उम्मीद है कि यह टिप्पणी इस बीच थोड़ी मदद करती है।memmapped