引言
科学计算跑得快不快,不只看 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 是瓶颈:串行写一个文件的问题
- 2. MPI-IO 基础:文件视图与独立 I/O
- 3. 集合 I/O:多个进程协作读写
- 4. HDF5:分层数据模型与并行写入
- 5. NetCDF:自描述科学数据格式
- 6. 与 Lustre 配合:条带化与数据布局
- 7. I/O 剖析与 Benchmark:Darshan 与 io500
- 8. 并行 I/O 的常见坑
- 9. 一套落地的并行 I/O 决策路径
- 10. 速查表与一句话记忆
- 延伸阅读
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 选型:
| HDF5 | NetCDF | |
|---|---|---|
| 定位 | 通用数据格式 | 地球/气候科学标准 |
| 模型 | Group/Dataset | Dimensions/Variables |
| 元数据 | Attribute | Attribute(更强) |
| 生态 | 广泛(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 |
| 系统 Benchmark | io500 / 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]] — 高性能计算专题
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。