从netCDF更快地阅读时间序列?

时间:2013-11-12 17:54:21

标签: r performance io hdf5 netcdf

我有一些大的netCDF文件,其中包含0.5度分辨率的地球的6小时数据。

每年有360个纬度点,720个经度点和1420个时间点。我有两个年度文件(12 GB ea)和一个110年数据(1.3 TB)的文件存储为netCDF-4(这里是1901数据的一个例子,1901.nc,它的use policy,和original, public files that I started with)。

根据我的理解,从一个netCDF文件读取而不是循环遍历文件集separated by year and variable originally provided应该更快。

我想为每个网格点提取一个时间序列,例如距离特定纬度和经度10或30年。但是,我发现这很慢。作为一个例子,从点位置读取10个值需要0.01秒,尽管我可以在0.002秒内从单个时间点读取10000个值的全局切片(维度的顺序是lat,lon,时间):

## a time series of 10 points from one location:
library(ncdf4)
met.nc <- nc_open("1901.nc")
system.time(a <- ncvar_get(met.nc, "lwdown", start = c(100,100,1), 
                                             count = c(1,1,10)))
   user  system elapsed 
  0.001   0.000   0.090 

## close down session

## a global slice of 10k points from one time
library(ncdf4)
system.time(met.nc <- nc_open("1901.nc"))
system.time(a <- ncvar_get(met.nc, "lwdown", start = c(100,100,1), 
                                             count = c(100,100,1)))
   user  system elapsed 
  0.002   0.000   0.002 

我怀疑这些文件是为了优化空间层的读取而编写的,因为a)变量的顺序是lat,lon,time,b)这将是生成这些文件的气候模型的逻辑顺序和c)因为全局范围是最常见的可视化。

我试图重新排序变量,以便时间先到时:

ncpdq -a time,lon,lat 1901.nc 1901_time.nc

ncpdq来自NCO (netCDF operators) software

> library(ncdf4)

## first with the original data set:
> system.time(met.nc <- nc_open("test/1901.nc"))
   user  system elapsed 
  0.024   0.045  22.334 
> system.time(a <- ncvar_get(met.nc, "lwdown", start = c(100,100,1), count = c(1, 1, 1000))
+ )
   user  system elapsed 
  0.005   0.027  14.958 

## now with the rearranged dimensions:
> system.time(met_time.nc <- nc_open("test/1901_time.nc"))
   user  system elapsed 
  0.025   0.041  16.704 
> system.time(a <- ncvar_get(met_time.nc, "lwdown", start = c(100,100,1), count = c(1, 1, 1000)))
   user  system elapsed 
  0.001   0.019   9.660 

如何在一个时间点优化一个点的读取时间序列而不是大面积的层?例如,如果文件的编写方式不同,例如time,lat,lon会更快吗?是否有一种“简单”的方法来转换netCDF-4文件中的维度顺序?

更新

(@mdsumner要求的基准)

library(rbenchmark)
library(ncdf4)
nc <- nc_open("1901.nc")
benchmark(timeseries = ncvar_get(nc, "lwdown", 
                                 start = c(1, 1, 50), 
                                 count = c(10, 10, 100)), 
          spacechunk = ncvar_get(nc, "lwdown", 
                                  start = c(1, 1, 50), 
                                  count = c(100, 100, 1)),           
          replications = 1000)

        test replications elapsed relative user.self sys.self user.child
2 spacechunk         1000   0.909    1.000     0.843    0.066          0
1 timeseries         1000   2.211    2.432     1.103    1.105          0
  sys.child
2         0
1         0

更新2:

我已经开始在这里开发解决方案了。这些碎片位于github.com/ebimodeling/model-drivers/tree/master/met/cruncep

中的一组脚本中

脚本仍需要一些工作和组织 - 并非所有脚本都有用。但阅读速度很快。与上述结果不完全相符,但在一天结束时,我可以立即从1.3TB文件(0.5度分辨率,2.5秒)中读取100年,6小时的时间序列:

system.time(ts <- ncvar_get(met.nc, "lwdown", start = c(50, 1, 1), count = c(160000, 1, 1)))
   user  system elapsed 
  0.004   0.000   0.004 

注意:维度的顺序已更改,如下所述:How can I specify dimension order when using ncdf4::ncvar_get?

3 个答案:

答案 0 :(得分:10)

我认为这个问题的答案不会重新排序数据,因为它会分块数据。有关分块netCDF文件含义的完整讨论,请参阅Unidata的主要netCDF开发人员Russ Rew的以下博客文章:

结果是,采用不同的分块策略可以大大提高访问速度,选择正确的策略并非易事。

在较小的样本数据集sst.wkmean.1990-present.nc上,我在使用您的基准测试命令时看到了以下结果:

1)Unchunked:

## test replications elapsed relative user.self sys.self user.child sys.child
## 2 spacechunk         1000   0.841    1.000     0.812    0.029          0         0
## 1 timeseries         1000   1.325    1.576     0.944    0.381          0         0

2)Naively Chunked:

## test replications elapsed relative user.self sys.self user.child sys.child
## 2 spacechunk         1000   0.788    1.000     0.788    0.000          0         0
## 1 timeseries         1000   0.814    1.033     0.814    0.001          0         0

天真的分块只是在黑暗中拍摄的;我这样使用了nccopy实用程序:

  

$ nccopy -c“lat / 100,lon / 100,time / 100,nbnds /”sst.wkmean.1990-present.nc chunked.nc

可以找到nccopy实用程序的Unidata文档here

我希望我能推荐一种特定的策略来分块你的数据集,但它高度依赖于数据。希望上面链接的文章能让您深入了解如何将数据分块以实现您正在寻找的结果!

更新

Marcos Hermida的以下博客文章展示了在阅读特定netCDF文件的时间序列时,不同的分块策略如何影响速度。这应该只是一个跳跃点。

关于通过nccopy显然悬挂的重新组合;该问题似乎与4MB的默认块高速缓存大小有关。通过将其增加到4GB(或更多),您可以将大文件的复制时间从24小时缩短到11分钟以下!

有一点我不确定;在第一个链接中,讨论与chunk cache有关,但传递给nccopy的参数-m指定了复制缓冲区中的字节数。 nccopy的-m参数控制块缓存的大小。

答案 1 :(得分:2)

编辑:原始问题有一个错误,但是开始阅读也可能有不同的开销,所以做多个代表是公平的。 rbenchmark让事情变得简单。

示例文件有点大,所以我使用了较小的文件,你可以对你的文件进行相同的比较吗?

更易于访问的示例文件: ftp://ftp.cdc.noaa.gov/Datasets/noaa.oisst.v2/sst.wkmean.1990-present.nc

我比时间序列花费的时间多两倍:

library(ncdf4)

nc <- nc_open("sst.wkmean.1990-present.nc")

library(rbenchmark)
benchmark(timeseries = ncvar_get(nc, "sst", start = c(1, 1, 50), count = c(10, 10, 100)), 
spacechunk = ncvar_get(nc, "sst", start = c(1, 1, 50), count = c(100, 100, 1)),           
replications = 1000)
##        test replications elapsed relative user.self sys.self user.child sys.child
##2 spacechunk         1000    0.47    1.000      0.43     0.03         NA        NA
##1 timeseries         1000    1.04    2.213      0.58     0.47         NA        NA

答案 2 :(得分:2)

不确定您是否考虑过cdo来提取这一点?

cdo remapnn,lon=x/lat=y in.nc point.nc 

有时CDO内存不足,如果发生这种情况,您可能需要循环遍历年度文件,然后使用

捕获单独的点文件
cdo mergetime point_${yyyy}.nc point_series.nc