h5py не придерживается спецификации фрагментов?

Проблема: у меня есть существующие файлы netCDF4 (их около 5000), (обычно в форме 96x3712x3712) точки данных (float32). Это файлы с первым измерением времени (1 файл в день), вторым и третьим пространственными измерениями. В настоящее время создание среза по первому измерению (даже частичного) может занять много времени по следующим причинам:

  • файлы netCDF разбиты на блоки размером 1x3712x3712. Нарезка по временному измерению в основном прочитает весь файл.
  • цикл (даже в нескольких процессах) по всем файлам меньшего размера также займет много времени.

Моя цель:

  • создавать ежемесячные файлы (около 2900x3712x3712) точек данных
  • оптимизировать их для разрезания во времени (размер фрагментов 2900x1x1 или немного больше в пространственных измерениях)

Другие требования:

  • файлы должны быть добавлены одной временной меткой (1x3712x3712), и этот процесс обновления должен занять менее 15 минут.
  • запрос должен быть достаточно быстрым: полный срез менее чем за одну секунду (то есть 2900x1x1) ==> на самом деле не так много данных ...
  • желательно, чтобы файлы были доступны для чтения несколькими процессами во время обновления.
  • Обработка исторических данных (других 5000 ежедневных файлов) должна занять менее пары недель.

Я пробовал уже несколько подходов:

  • объединение файлов netcdf и их повторное объединение ==> занимает слишком много памяти и слишком много времени ...
  • запись их из pandas в файл hdf (с использованием pytables) ==> создает широкую таблицу с огромным индексом. Это, в конечном итоге, также займет слишком много времени для чтения и потребует разбиения набора данных по пространственным измерениям из-за ограничений метаданных.
  • мой последний подход заключался в записи их в файл hdf5 с помощью h5py:

Вот код для создания одного ежемесячного файла:

import h5py
import pandas as pd
import numpy as np

def create_h5(fps):
    timestamps=pd.date_range("20050101",periods=31*96,freq='15T') #Reference time period
    output_fp = r'/data/test.h5'
    try:
        f = h5py.File(output_fp, 'a',libver='latest')
        shape = 96*nodays, 3712, 3712
        d = f.create_dataset('variable', shape=(1,3712,3712), maxshape=(None,3712,3712),dtype='f', compression='gzip', compression_opts=9,chunks=(1,29,29))
        f.swmr_mode = True
        for fp in fps:
            try:
                nc=Dataset(fp)
                times = num2date(nc.variables['time'][:], nc.variables['time'].units)
                indices=np.searchsorted(timestamps, times)
                for j,time in enumerate(times):
                    logger.debug("File: {}, timestamp: {:%Y%m%d %H:%M}, pos: {}, new_pos: {}".format(os.path.basename(fp),time,j,indices[j]))
                    d.resize((indices[j]+1,shape[1],shape[2]))
                    d[indices[j]]=nc.variables['variable'][j:j+1]
                    f.flush()
            finally:
                nc.close()
    finally:
        f.close()
    return output_fp

Я использую последнюю версию HDF5, чтобы иметь возможность SWMR. Аргумент fps - это список путей к файлам ежедневного netCDF4. Он создает файл (на ssd, но я вижу, что создание файла в основном связано с процессором) примерно за 2 часа, что приемлемо.

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

Теперь проблема: разрезание на NAS с настройкой RAID 6 занимает около 20 секунд, чтобы разрезать измерение времени, даже если оно находится в одном фрагменте ...

Я полагаю, что даже если он находится в одном фрагменте файла, поскольку я записал все значения в цикле, он должен быть каким-то образом фрагментирован (хотя не знаю, как этот процесс работает). Вот почему я попытался сделать h5repack, используя инструменты CML HDF5 в новый файл, с теми же фрагментами, но, надеюсь, переупорядочив значения, чтобы запрос мог читать значения в более последовательном порядке, но безуспешно. Несмотря на то, что для выполнения этого процесса потребовалось 6 часов, он не повлиял на скорость запроса.

Если я правильно делаю свои вычисления, чтение одного фрагмента (2976x32x32) занимает всего несколько МБ (11 МБ без сжатия, только немного больше, чем 1 МБ со сжатием, я думаю). Как это может длиться так долго? Что я делаю неправильно? Я был бы рад, если бы кто-нибудь пролил свет на то, что на самом деле происходит за кулисами ...


person Elvin    schedule 03.07.2017    source источник


Ответы (1)


Влияние размера блока

В худшем случае чтение и запись одного фрагмента можно рассматривать как случайную операцию чтения / записи. Основное преимущество SSD - это скорость чтения или записи небольших фрагментов данных. Жесткий диск справляется с этой задачей намного медленнее (можно наблюдать коэффициент 100), а NAS может быть даже намного медленнее, чем жесткий диск.

Так что решением проблемы будет больший размер чанка. Некоторые тесты в моей системе (Core i5-4690).

Exampe_1 (размер блока (1,29,29) = 3,4 КБ):

import numpy as np
import tables #needed for blosc
import h5py as h5
import time
import h5py_cache as h5c

def original_chunk_size():
    File_Name_HDF5='some_Path'
    #Array=np.zeros((1,3712,3712),dtype=np.float32)
    Array=np.random.rand(96,3712,3712)

    f = h5.File(File_Name_HDF5, 'a',libver='latest')
    f.swmr_mode = True
    nodays=1

    shape = 96*nodays, 3712, 3712
    d = f.create_dataset('variable', shape, maxshape=(None,3712,3712),dtype='f',chunks=(1,29,29),compression=32001,compression_opts=(0, 0, 0, 0, 9, 1, 1), shuffle=False)

    #Writing
    t1=time.time()
    for i in xrange(0,96*nodays):
        d[i:i+1,:,:]=Array

    f.close()
    print(time.time()-t1)

    #Reading
    f = h5.File(File_Name_HDF5, 'a',libver='latest')
    f.swmr_mode = True
    d=f['variable']

    for i in xrange(0,3712,29):
        for j in xrange(0,3712,29):
            A=np.copy(d[:,i:i+29,j:j+29])

    print(time.time()-t1)

Результаты (запись / чтение):

SSD: 38 сек / 54 сек

HDD: 40с / 57с

NAS: 252 с / 823 с

Во втором примере я буду использовать h5py_chache, потому что я не хочу поддерживать предоставление фрагментов (1,3712,3712). Стандартный размер chunk-chache-size составляет всего один МБ, поэтому его необходимо изменить, чтобы избежать множественных операций чтения / записи на фрагментах. https://pypi.python.org/pypi/h5py-cache/1.0

Exampe_2 (размер блока (96,58,58) = 1,3 МБ):

import numpy as np
import tables #needed for blosc
import h5py as h5
import time
import h5py_cache as h5c

def modified_chunk_size():
    File_Name_HDF5='some_Path'
    Array=np.random.rand(1,3712,3712)

    f = h5c.File(File_Name_HDF5, 'a',libver='latest', 
    chunk_cache_mem_size=6*1024**3)
    f.swmr_mode = True
    nodays=1

    shape = 96*nodays, 3712, 3712
    d = f.create_dataset('variable', shape, maxshape=(None,3712,3712),dtype='f',chunks=(96,58,58),compression=32001,compression_opts=(0, 0, 0, 0, 9, 1, 1), shuffle=False)

    #Writing
    t1=time.time()
    for i in xrange(0,96*nodays):
        d[i:i+1,:,:]=Array

    f.close()
    print(time.time()-t1)

    #Reading
    f = h5c.File(File_Name_HDF5, 'a',libver='latest', chunk_cache_mem_size=6*1024**3) #6 GB chunk chache
    f.swmr_mode = True
    d=f['variable']

    for i in xrange(0,3712,58):
        for j in xrange(0,3712,58):
            A=np.copy(d[:,i:i+58,j:j+58])

    print(time.time()-t1)

Результаты (запись / чтение):

SSD: 10 с / 16 с

HDD: 10 с / 16 с

NAS: 13 с / 20 с

Скорость чтения / записи может быть дополнительно улучшена за счет минимизации вызовов API (чтения и записи больших блоков фрагментов).

Еще не хочу упоминать о методе компрессии. Blosc может достигать пропускной способности до 1 ГБ / с (узкое место ЦП). Gzip медленнее, но обеспечивает лучшую степень сжатия.

d = f.create_dataset('variable', shape, maxshape=(None,3712,3712),dtype='f',chunks=(96,58,58),compression='gzip', compression_opts=3)

Размер файла 20s / 30s: 101 МБ

d = f.create_dataset ('переменная', форма, maxshape = (Нет, 3712,3712), dtype = 'f', chunks = (96,58,58), сжатие = 'gzip', сжатие_opts = 6)

Размер файла 50s / 58s: 87 МБ

d = f.create_dataset ('переменная', форма, maxshape = (Нет, 3712,3712), dtype = 'f', chunks = (96,58,58), сжатие = 'gzip', сжатие_opts = 9)

Размер файла 50s / 60s: 64 МБ

А теперь эталон на целый месяц (30 дней). Запись немного оптимизирована и записывается с использованием (96,3712, 3712).

def modified_chunk_size():
    File_Name_HDF5='some_Path'

    Array_R=np.random.rand(1,3712,3712)
    Array=np.zeros((96,3712,3712),dtype=np.float32)
    for j in xrange(0,96):
        Array[j,:,:]=Array_R

    f = h5.File(File_Name_HDF5, 'a',libver='latest')
    f.swmr_mode = True
    nodays=30

    shape = 96, 3712, 3712
    d = f.create_dataset('variable', shape, maxshape=(None,3712,3712),dtype='f',chunks=(96,58,58),compression=32001,compression_opts=(0, 0, 0, 0, 9, 1, 1), shuffle=False)

    #Writing
    t1=time.time()
    for i in xrange(0,96*nodays,96):
        d[i:i+96,:,:]=Array
        d.resize((d.shape[0]+96,shape[1],shape[2]))

    f.close()
    print(time.time()-t1)

    #Reading
    f = h5.File(File_Name_HDF5, 'a',libver='latest')
    f.swmr_mode = True
    d=f['variable']
    for i in xrange(0,3712,58):
        for j in xrange(0,3712,58):
            A=np.copy(d[:,i:i+58,j:j+58])

    print(time.time()-t1)

133s / 301s с blosc

432 с / 684 с с gzip compress_opts = 3

У меня были те же проблемы при доступе к данным на NAS. Надеюсь, это поможет...

person max9111    schedule 07.07.2017
comment
Спасибо за понимание. - person Elvin; 14.07.2017