Обработка очень больших файлов netCDF в python

Я пытаюсь работать с данными из очень больших файлов netCDF (~ 400 ГБ каждый). Каждый файл имеет несколько переменных, все они намного больше, чем системная память (например, 180 ГБ против 32 ГБ ОЗУ). Я пытаюсь использовать numpy и netCDF4-python для выполнения некоторых операций с этими переменными, копируя фрагмент за раз и работая с этим фрагментом. К сожалению, чтение каждого фрагмента занимает очень много времени, что снижает производительность.

Например, одна из переменных представляет собой массив формы (500, 500, 450, 300). Я хочу работать со срезом [:,:,0], поэтому делаю следующее:

import netCDF4 as nc

f = nc.Dataset('myfile.ncdf','r+')
myvar = f.variables['myvar']
myslice = myvar[:,:,0]

Но последний шаг занимает очень много времени (~ 5 минут в моей системе). Если, например, я сохранил переменную формы (500, 500, 300) в файле netcdf, то операция чтения того же размера займет всего несколько секунд.

Есть ли способ ускорить это? Очевидным путем было бы транспонировать массив так, чтобы индексы, которые я выбираю, появлялись первыми. Но в таком большом файле это было бы невозможно сделать в памяти, и кажется еще медленнее пытаться сделать это, учитывая, что простая операция и так занимает много времени. Что мне нужно, так это быстрый способ чтения фрагмента файла netcdf в стиле функции get_vara интерфейса Fortran. Или какой-то способ эффективного переноса массива.


person tiago    schedule 22.08.2012    source источник
comment
Если вы хотите сделать с данными больше, чем просто переместить их, взгляните на xarray модуль: предоставляет очень удобный интерфейс для dask массивов с нехваткой памяти.   -  person j08lue    schedule 25.04.2016


Ответы (2)


Вы можете транспонировать переменные netCDF, слишком большие для размещения в памяти, с помощью утилиты nccopy, которая задокументирована здесь:

http://www.unidata.ucar.edu/netcdf/docs/guide_nccopy.html

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

person Russ Rew    schedule 22.08.2012
comment
Спасибо Русс за ответ. Это было очень интересно, так как я никогда особо не интересовался чанкингом. Предположим, у меня есть переменная с размерами (500, 500, 300, 400). Если я делаю фрагментацию 1 в третьем измерении, будет ли это аналогично выполнению частичного транспонирования, где эта ось является самой быстрой (т. Е. Непрерывной)? Я изменил разбивку по оси, о которой собирался прочитать подробнее, но получение 3D-среза по-прежнему занимает очень много времени. Я выясню, является ли это проблемой файловой системы/сети. - person tiago; 23.08.2012
comment
Нет, создание длины фрагмента в 3-м измерении, равной 1, делает это измерение самым медленным, поскольку при чтении по этому измерению вы будете получать доступ к фрагменту размером 400 МБ для каждого 4-байтового значения. Но если вы используете 10 фрагментов по каждому измерению (каждый фрагмент 50x40x30x40), каждый фрагмент будет содержать около 12 МБ (при условии, что 4 байта на значение), и потребуется всего 10 операций чтения, чтобы получить доступ к цилиндру значений по любому измерению (блок 50x50x30x40). ). Пример того, как это может улучшить время доступа в некоторых направлениях, см. на двух слайдах: unidata.ucar.edu/netcdf/workshops/2011/chunk_cache/Problem.html - person Russ Rew; 27.08.2012
comment
Исправление к комментарию выше: замените (блок 50x50x30x40) на (10 блоков 50x50x30x40)... - person Russ Rew; 27.08.2012
comment
Я немного смущен. Предполагая размер переменной (500, 500, 300, 400), мне нужен быстрый доступ к срезам, например (:, :, 0, 0). Я думал, что фрагментация с 1 в последних двух измерениях будет лучше всего (кроме транспонирования всего этого). Каков наилучший фрагмент для такого доступа? В вашей ссылке говорится, что повторная разбивка с большими значениями первого измерения и меньшими значениями последних измерений ускорит доступ к этим последним измерениям, но вы, кажется, говорите об обратном. - person tiago; 27.08.2012

Это комментарий, а не ответ, но я не могу комментировать вышеизложенное, извините.

Насколько я понимаю, вы хотите обработать myvar[:,:,i] с i в range(450). В этом случае вы собираетесь сделать что-то вроде:

for i in range(450):
    myslice = myvar[:,:,i]
    do_something(slice)

узким местом является доступ к myslice = myvar[:,:,i]. Вы пытались сравнить, сколько времени требуется для доступа к moreslices = myvar[:,:,0:n]? Это будут непрерывные данные, и, возможно, вы сможете сэкономить время с этим. Вы бы выбрали n такого размера, какой позволяет ваша память, а затем обработали бы следующий блок данных moreslices = myvar[:,:,n:2n] и так далее.

person gg349    schedule 22.08.2012
comment
Спасибо за ваш ответ. Я сравнил доступ к myvar[:,:,0:n], и он занимает примерно столько же времени, сколько и myvar[:,:,0]. Так что это хотя бы способ, но я все еще пытаюсь выяснить, почему существует такой штраф для начала. Обратите внимание, что myvar[:,:,0:n] не является смежным. - person tiago; 23.08.2012
comment
Что ж, это правда, что myvar[1,0,0] не граничит с myvar[2,0,0]. Но это занимает примерно столько же времени, потому что myvar[i,i,0] на самом деле граничит с myvar[i,i,1]. Теперь это имеет больше смысла? - person gg349; 23.08.2012