
从买不起硬盘,到开始抠每个字节
前一篇里我讲了一个「消费降级」的故事:Python | 压缩近3倍netcdf文件大小
由于最近的工作:需要处理数十万文份ERA5 137 层数据。
然后,很自然地,我碰到了下一个更 “狠” 的问题。
需要处理 数十万个 ERA5 的 137 层数据。 典型工作流大概是这样:
❝硬盘会被你喂到直接“超重爆炸”。 所以,这次不但得动脑子压缩数据,还得从「网格本身」下手。
即使是放在服务器上,也要花费不小的空间。

从 F96 / N96 到 O96:为硬盘省点命 简单回顾一下三个网格的差别(以 N=96 为例):
F:Full Gaussian / 规则经纬网

N:传统 reduced Gaussian(N-grid)


O:Octahedral reduced Gaussian(O-grid)
它是一种缩减高斯网格的形式,其纬线数量及所在的纬度值与原始高斯网格相同,但在极点与赤道之间的每个纬线圈上,经度点的数量是根据以下公式计算的:


处理了几个用于测试的 ERA5 文件,大概是这样的(粗略示意):
也就是说:
仅仅换成 八面体网格,在保持分辨率的前提下,整体存储就能再省一截。

八面体高斯网格 O96 是什么鬼?
❝用大白话说:传统 N-grid 在极区点太挤、赤道点不够“均匀”,对谱模式来说有些不理想; O-grid 通过设计一个“八面体结构”的 reduced Gaussian grid,在 球面上格点分布更均匀(更接近等面积); 格点数稍微减少,但对数值精度影响很小甚至更好; 仍然保持高斯纬度的优点(方便谱-格点变换)。

现实问题是:
面对数以十万计的 ERA5 文件,如果采用传统的常规流程:
先用 CDO 命令行把原文件插值成规则网格 存出一个巨大的中间 NetCDF 文件 用 Python 读进来做物理量计算 最后保存结果。
这会带来一个致命问题:极其恐怖的硬盘 I/O(读写)瓶颈,你的硬盘大部分时间都在痛苦地来回搬运中间临时文件,处理速度会被严重拖垮。
为了极限压榨计算性能,保护我那可怜的硬盘,我的计划是全程在运行内存(RAM)中“空手套白狼”:
直接读取原始的 O/N 缩减网格数据进入内存;
在内存中“即时(On-the-fly)” 完成到规则网格的插值重构;
直接进行所需的物理量诊断与计算;
最后只把计算好的目标变量保存。
全程绝不输出任何未经处理的中间网格文件,把沉重的读写负担全部转移到高速运行内存中

想法很美好,但实操起来却卡壳了:
强大的 CDO 严重依赖本地文件的读写,很难丝滑地嵌入这种纯内存的 Python 数据流中。
如果用 xarray 和 scipy 自己写插值,不仅要手动解析反人类的 pl 数组和缩减网格逻辑,代码还会变得极其啰嗦且运行缓慢。

为了彻底打通这个“内存级”的网格转换瓶颈,干脆自己造了个极速小轮子:
gaussregular——一个专门将 reduced Gaussian 网格(包括最新的 O-grid)极速转换为规则高斯网格的轻量库。
系统要求:
要安装gaussregular库,可以使用pip:
pip install gaussregular或者使用以下命令:
pip install -U --index-url https://pypi.org/simple/ gaussregular库的仓库在这里:
❝https://github.com/QianyeSu/GaussRegular
功能很简单也很聚焦:
我们用 O96 的来看:
import xarray as xrimport gaussregular as grpath_o96 = r"era5_reanalysis_O96.grib2"# 读取 O96 reduced Gaussian gridds = xr.open_dataset( path_o96, engine="cfgrib")print("raw dims:", ds.dims)da = ds["t"] # 举例用温度print("raw var:", da.name, da.dims, da.shape)print("GRIB_gridType:", da.attrs.get("GRIB_gridType"))print("GRIB_N:", da.attrs.get("GRIB_N"))# 创建 regularizer,指定 N=96 方便全局/区域判断engine = gr.GaussRegularizer(method="linear", cache=True, grid_number=96)# 直接对 DataArray 做规则化插值regular_da = engine.regularize_xarray(da, method="linear") # 或 "nearest"print("regularized shape:", regular_da.shape)print("dims:", regular_da.dims)在我的测试里,大致会输出类似:
regular_ds = engine.regularize_dataset(ds, method="linear")print(regular_ds)它会自动:

这样算下来,大约又省了十倍的空间,原先存储需要1GB的数据,现在只需要100多MB就可以存储相同的数据量。
❝详细使用方法请看:https://github.com/QianyeSu/GaussRegular