并行 I/O 实战:MPI-IO、HDF5 与 NetCDF 数据读写

大规模科学计算中,I/O 往往是隐藏的瓶颈。本文系统讲解并行 I/O:MPI-IO 的独立/集合文件视图、HDF5 的数据模型与并行写入、NetCDF 的自描述格式、Lustre 条带化与数据布局优化、I/O 性能剖析(Darshan/io 500),以及从串行读写升级到并行 I/O 的真实路径。

引言

科学计算跑得快不快,不只看 FLOPS——I/O 常常是隐藏瓶颈。千万核作业算完,结果写不进文件系统照样卡死。并行 I/O 的核心矛盾是:计算是分布式的,而「一个文件」默认是串行的。MPI-IO、HDF5、NetCDF 这三层技术把「多进程同时写一个文件」变成工程现实。

本文按「问题 → 机制 → 工具 → 实践」讲解:串行 I/O 为什么慢、MPI-IO 的文件视图与集合 I/O、HDF5 数据模型与并行写入、NetCDF 自描述格式、与 Lustre 条带化的配合、I/O 剖析与 benchmark,最后给出一套可落地的并行 I/O 决策路径。

前置:/hpc-mpi-basics/(MPI 集合操作)、/hpc-lustre-filesystem/(并行文件系统)、/hpc-memory-hierarchy/(存储层次)。


目录


1. 为什么 I/O 是瓶颈:串行写一个文件的问题

直觉:1000 个进程算完数据,要写一个 1 TB 的结果文件。

方案一:每个进程写自己的文件 → 1000 个小文件
  问题:管理混乱、文件爆炸、后续处理要合并

方案二:全部进程写同一个文件
  问题:谁先写?写哪一段?—— 不加控制就是灾难

串行 I/O 的本质问题:

□ POSIX 语义:一个 fd 一个 offset,天然串行
□ 锁与一致:多进程写同一文件需要协调
□ 元数据风暴:大量小文件创建打爆 MDS
□ 网络往返:单进程读写吃不满并行文件系统带宽

记忆:I/O 瓶颈 = 「计算分布式、文件串行」的错位——并行 I/O 的目标就是让「多进程协作写一个文件」又快又一致。


2. MPI-IO 基础:文件视图与独立 I/O

MPI-IO(MPI-2 标准) 让每个进程声明「自己负责文件的哪一段」,用文件视图描述:

#include <mpi.h>

MPI_File fh;
MPI_Offset offset;   // 起始偏移
MPI_Datatype view;   // 数据类型(描述每个进程的数据布局)

// 打开文件(并行)
MPI_File_open(MPI_COMM_WORLD, "out.dat", MPI_MODE_CREATE | MPI_MODE_WRONLY,
               MPI_INFO_NULL, &fh);

// 定义文件视图:进程 i 负责 [i*n, (i+1)*n) 区间
MPI_File_set_view(fh, offset, MPI_INT, view, "native", MPI_INFO_NULL);

// 写各自的数据块
MPI_File_write(fh, buf, n, MPI_INT, MPI_STATUS_IGNORE);

MPI_File_close(&fh);

关键概念:

□ 文件视图(File View):每个进程看到的「子文件」
□ 独立 I/O:各进程各自发 write,不协调
□ 可移植性:显式偏移(explicit offset)vs 共享指针

MPI-IO 提供的 API:

API说明
MPI_File_write/read独立写/读
MPI_File_write_at显式偏移写
MPI_File_write_all集合写(所有进程参与)
MPI_File_seek移动共享指针

认知:MPI-IO 给「多进程写一文件」定义了标准接口——文件视图把一维字节流映射到各进程的二维/三维数据。


3. 集合 I/O:多个进程协作读写

集合 I/O(Collective I/O) 让所有进程一起参与读写,这是并行 I/O 提速的关键:

// 所有进程一起写(两个阶段)
MPI_File_write_all(fh, buf, n, MPI_INT, MPI_STATUS_IGNORE);

集合 I/O 的原理:两阶段 I/O(Two-Phase I/O)

第一阶段:各进程把数据发给「负责该文件区域的进程」(数据聚合)
第二阶段:这些进程把合并后的大块数据写入文件系统
  → 用「大而少」的 I/O 请求替代「小而多」
  → 减少元数据操作与网络往返

为什么要聚合:

□ 文件系统喜欢大块连续写(条带友好)
□ 网络往返次数 = 数据块数,越少越好
□ 元数据压力骤降

记忆:集合 I/O = 先「内存里合并」再「整块写盘」——用大数据块换小请求数,是让并行文件系统吃饱的关键。


4. HDF5:分层数据模型与并行写入

HDF5 是科学计算的通用数据格式:分层文件结构 + 任意数据集 + 并行写入。

# Python h5py:并行写
import h5py
from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.Get_rank()

# 打开并行 HDF5 文件
with h5py.File("data.h5", "w", driver="mpio", comm=comm) as f:
    dset = f.create_dataset("field", shape=(comm.size, 100), dtype="f8")
    dset[rank, :] = rank * 1.0   # 每个进程写自己那行

HDF5 核心概念:

□ File:分层容器(类似文件系统)
□ Group:目录
□ Dataset:数据数组(多维 + 元数据)
□ Attribute:附加元数据(单位/描述)
□ Chunking:数据集分块存储(压缩/局部访问)

并行 HDF5 写(h5py driver=mpio):

□ 底层走 MPI-IO(MPI_File_write_all)
□ 多个进程可同时写同一 dataset 的不同片
□ 支持 chunking + 压缩(数据集分区)

落地:HDF5 = 「带元数据的并行二进制」——dataset 分片、group 组织、attribute 描述,是科学数据的瑞士军刀。


5. NetCDF:自描述科学数据格式

NetCDF 面向地球科学/气候数据:自描述 + 平台无关 + 数组化。

维度(dimensions):time / lat / lon / level
变量(variables):temperature(time, lat, lon)
属性(attributes):units="celsius"、_FillValue
# Python netCDF4:并行写
from netCDF4 import Dataset

nc = Dataset("climate.nc", "w", parallel=True, comm=comm)
nc.createDimension("time", 10)
nc.createDimension("lat", 100)
temp = nc.createVariable("temp", "f4", ("time", "lat"))

# 每个进程写自己的时间切片
temp[rank:rank+1, :] = rank * 1.0
nc.close()

NetCDF vs HDF5 选型:

HDF5NetCDF
定位通用数据格式地球/气候科学标准
模型Group/DatasetDimensions/Variables
元数据AttributeAttribute(更强)
生态广泛(h5py/HDF5 C++)气候/海洋/大气主导
关系NetCDF-4 底层可存 HDF5—

记忆:NetCDF = 「地球科学界的 HDF5」——维度/变量/属性三件套,自描述让数据几十年后仍可读。


6. 与 Lustre 配合:条带化与数据布局

并行文件系统(Lustre)的布局直接决定 I/O 速度:

Lustre 存储:文件被切分成条带(stripe)分布在多个 OST 上
  → 条带数(stripe count)+ 条带大小(stripe size)
  → 越多 OST 参与,聚合带宽越高

关键调优:

□ 大文件:增大 stripe count(默认 1 不够)
□ 集合 I/O 与条带匹配:一个进程组对应一段条带
□ 避免共享文件竞争:不同作业用不同目录/OST 池
□ 小文件:不要开大条带(元数据开销)

lfs 命令:

# 设置条带:8 个 OST,大小 64MB
lfs setstripe -c 8 -S 64M /path/output_dir
lfs getstripe /path/output_file   # 查看布局

记忆:Lustre 调优 = 让「文件条带」匹配「并行进程」——stripe count 开足、stripe size 匹配块大小,聚合带宽才能吃满。


7. I/O 剖析与 Benchmark:Darshan 与 io500

不能靠感觉调 I/O——要量:

Darshan(I/O 剖析):透明记录应用的文件访问模式:

# 链接 Darshan 后运行,生成 .darshan 日志
mpirun -np 16 ./app
darshan-parser app.darshan | less   # 查看 I/O 统计

Darshan 能回答:

□ 每个文件读写多少?读多还是写多?
□ 访问模式:顺序 / 随机 / 小请求?
□ 各进程是否均衡?(负载倾斜)
□ 集合 I/O 是否生效?

io500(Benchmark):衡量文件系统综合能力:

□ IOR:带宽/IOPS(读、写、混合)
□ mdtest:元数据操作
□ 综合分:反映真实工作负载
□ 跑分对比:TOP500 同样有 I/O 榜单

心法:调 I/O 先问「访问模式是什么」——Darshan 告诉你现实,io500 告诉你系统上限,两者对照才有方向。


8. 并行 I/O 的常见坑

实战中反复踩的坑:

□ 忘开集合 I/O:用 MPI_File_write 而不是 _all → 小请求风暴
□ 条带没调:默认 1 个 OST,大文件慢
□ 每进程一个文件再合并:元数据风暴 + 后续串行
□ 检查点不并行:checkpoint 写成串行 → 白算
□ 同步粒度过大:每次写都 barrier,I/O 串行化
□ 压缩/转换在 I/O 路径上:CPU 和 I/O 争抢

好的做法:

□ 优先集合 I/O + 大块连续写
□ 检查点用并行格式(HDF5 并行/NetCDF 并行)
□ 布局对齐:数据分块与 Lustre 条带对齐
□ 异步 I/O:计算与写盘重叠
□ 性能门禁:每次优化用 Darshan/io500 验证

记忆:并行 I/O 的坑集中在「不集合、不调条带、不同步控制」——三大纪律:集合 I/O、条带对齐、异步重叠。


9. 一套落地的并行 I/O 决策路径

从零开始选型:

1. 数据要不要「多进程共写一文件」?
     ├─ 否 → 各进程独立文件(或小文件批量)
     └─ 是 → 进入下一步
2. 用什么格式?
     ├─ 地球/气候数据 → NetCDF
     ├─ 通用科学数据 → HDF5
     └─ 简单数组 → MPI-IO 裸格式
3. 写的方式?
     ├─ 集合 I/O(默认优先)
     └─ 异步 + 分阶段流水
4. 文件系统配合?
     ├─ 大文件调条带(stripe count/size)
     └─ 目录/池隔离
5. 验证?
     ├─ Darshan 剖析访问模式
     └─ io500/IOR 对比基线

一个最小并行写流程(MPI-IO):

MPI_File_open(...);
MPI_File_set_view(...);       // 定义各进程视图
MPI_File_write_all(...);      // 集合写
MPI_File_close(...);

路径:先定「要不要共写一文件」→ 再选格式(NetCDF/HDF5/裸 MPI-IO)→ 集合 I/O + 条带对齐 → Darshan 验证——一步步把 I/O 从瓶颈变成快路径。


10. 速查表与一句话记忆

需求手段
多进程共写一文件MPI-IO(文件视图)
提速关键集合 I/O(两阶段聚合)
通用科学格式HDF5(Group/Dataset/Chunk)
地球科学格式NetCDF(维度/变量/属性)
条带调优lfs setstripe -c N -S 64M
I/O 剖析Darshan
系统 Benchmarkio500 / IOR / mdtest
检查点并行 HDF5/NetCDF
常见事故小请求风暴 / 忘集合 / 条带没调

一句话记忆:并行 I/O = MPI-IO 定文件视图 + 集合 I/O 聚合数据块 + HDF5/NetCDF 定格式 + Lustre 条带对齐——「多进程算、协同写一文件」,用大块连续 I/O 喂饱并行文件系统,Darshan 量、io500 比。


延伸阅读

  • /hpc-mpi-basics/ — MPI 集合操作与进程拓扑
  • /hpc-lustre-filesystem/ — 并行文件系统与条带化
  • /hpc-memory-hierarchy/ — 存储层次与内存墙
  • /hpc-fault-tolerance/ — 并行检查点的 I/O 设计
  • /hpc-performance-profiling/ — 剖析方法学与 I/O 分析
  • [[hpc]] — 高性能计算专题

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「hpc」更多文章

  1. ARM 超算与专用加速器:A64FX 与 NVIDIA Grace
  2. HPC 与 AI 融合:超算跑大模型训练
  3. 绿色 HPC:能耗优化与功率封顶实战