filestream默认在lustre上变慢,因其4096缓冲小、禁用异步、不感知条带,导致缓存失效与冗余拷贝;应设大缓冲(≥1mb)、启用asynchronous与sequentialscan、避免streamreader/writer封装。

为什么 FileStream 默认行为在 Lustre 上容易变慢
Lustre 是为大规模并行 IO 设计的,但 .NET 的 FileStream 默认启用缓冲(bufferSize=4096)、禁用异步 IO 优化、且不感知条带(stripe)布局。结果就是小块随机读写频繁触发客户端缓存失效,大文件顺序读写又因内核页缓存与 Lustre 自身缓存叠加造成冗余拷贝。
- 避免使用
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 4096, false)这类默认构造——4096缓冲太小,false关闭异步会阻塞线程池 - 显式设置
bufferSize≥ Lustre 条带大小(常用1048576即 1MB),可通过lctl getstripe -v <path></path>查看 - 务必传入
FileOptions.Asynchronous | FileOptions.SequentialScan,让内核走 direct I/O 路径并提示 Lustre 客户端做预取 - 不要依赖
StreamReader/StreamWriter包装FileStream做文本操作——它们自带额外缓冲层,和 Lustre 的 striping 不对齐,易引发跨 OST 写放大
如何让 WriteAsync 真正并发写入不同 OST
Lustre 的并行写性能取决于能否把数据分散到多个对象存储目标(OST)。.NET 默认的单 FileStream 实例无法自动分片;必须手动按字节偏移切分任务,并为每个分片创建独立 FileStream 实例绑定到同一文件路径(Lustre 支持多客户端并发写同文件)。
- 先用
lctl getstripe -c <path></path>获取 OST 数量(如4),再按fileSize / ostCount切逻辑块 - 每个任务用独立
FileStream打开同一文件,设置FileAccess.Write+FileShare.Write+FileOptions.Asynchronous - 调用
stream.Seek(offset, SeekOrigin.Begin)定位,再await stream.WriteAsync(buffer, offsetInBuffer, count)——注意:offset 是文件内偏移,不是 buffer 偏移 - 别用
Parallel.ForEach直接跑FileStream操作:线程池饥饿会导致异步回调堆积,改用Task.WhenAll(tasks)控制并发度(建议 ≤ OST 数 × 2)
IOException 报 “Invalid argument” 或 “No space left on device” 的真实原因
这两类错误在 Lustre 上几乎从不表示磁盘满或参数错,而是客户端与 MDS/OST 协议失配或 stripe 不一致:
-
"Invalid argument"多见于:用FileOptions.WriteThrough但 Lustre 客户端未挂载-o flock;或写入 size 超过单 OST 最大文件尺寸(查lctl get_param osc.*.max_pages_per_rpc) -
"No space left on device"常因:文件创建时未继承父目录 stripe 设置(检查lctl getstripe -d <parent></parent>),导致新文件只落在 1 个 OST 上,即使总空间充足也会报满 - 所有 IO 前先确保文件已用
lctl setstripe显式配置好 stripe_count/stripe_size,C# 层不做任何 stripe 感知操作——Lustre 客户端驱动负责分发,应用只需按需读写偏移
绕过 .NET 文件抽象直接调用 Lustre ioctl 的边界场景
极少数情况需要控制 stripe hint(比如追加写时强制新块落到特定 OST),就得跳过 FileStream,用 System.IO.FileStream.SafeFileHandle + P/Invoke 调用 ioctl。
- 仅限 Linux 环境(Windows Subsystem for Linux 不支持 Lustre ioctl),且需
libc>= 2.27 - 关键 ioctl 是
LL_IOC_LOV_SETSTRIPE(需 root 权限)和LL_IOC_LOV_GETSTRIPE(普通用户可读) - 不要在生产代码里自己拼
lov_user_md结构体——用liblustreapi的 C 绑定更稳,C# 侧只做DllImport("liblustreapi.so")调用封装 - 绝大多数 HPC 场景不需要此操作:正确设置目录 stripe + 合理分块写入,比 runtime 动态干预 OST 分布更可靠
真正卡住性能的往往不是代码写法,而是 Lustre 客户端挂载参数(比如没开 -o flock 或 -o localflock)和目录 stripe 配置——这些在 C# 里根本控制不了,得和系统管理员对齐。








