应抽象存储逻辑为统一接口ifilestorage,分别用本地filestream(绕过缓存)和s3(禁重试、限流、预热)实现,使用stopwatch.gettimestamp()高精度计时,每轮50次测试剔除首尾10%极值取中位数,并控制并发与环境干扰以确保a/b对比可靠。

怎么用 C# 模拟本地和 S3 的读写做对比
直接测真实 S3 会受网络抖动、临时限流、签名开销干扰,导致 A/B 数据不可靠。得把「存储逻辑抽象掉」,再用相同数据集、相同循环次数、相同 warm-up 策略跑两套实现。
实操建议:
- 定义统一接口
IFileStorage,含UploadAsync(string key, Stream data)和DownloadAsync(string key) - 本地实现用
FileStream+Directory.CreateDirectory,路径固定(比如"./bench-temp"),每次测试前清空 - S3 实现用
AwsSdk的AmazonS3Client.PutObjectAsync,但必须关掉自动重试(new AmazonS3Config { MaxErrorRetry = 0 }),否则失败重试会污染耗时统计 - 所有测试用同一份 1MB 随机字节数组(
Random.Shared.NextBytes),避免 GC 或磁盘缓存干扰
为什么 Stopwatch.GetTimestamp() 比 DateTime.Now 更准
DateTime.Now 是系统时钟,精度通常只有 10–15ms,且可能被 NTP 调整跳变;而 Stopwatch.GetTimestamp() 直接读取 CPU 高精度计数器(TSC),在现代 Windows 上能到 100ns 级别,适合测毫秒级操作。
实操建议:
- 不用
Stopwatch.StartNew()—— 它有初始化开销,改用var start = Stopwatch.GetTimestamp(); ... var elapsed = Stopwatch.GetTimestamp() - start; - 换算成毫秒要除以
Stopwatch.Frequency / 1000.0,不是硬写 10000 - 每轮测试至少跑 50 次,剔除首尾各 10% 极值,再算中位数——单次
PutObjectAsync可能因 DNS 解析卡顿,不能信均值
本地文件测试容易漏掉的三个坑
你以为本地快就稳了?NTFS 缓存、AV 扫描、OneDrive 同步都会偷偷拖慢你,而且这些在 CI 环境里未必存在,导致本地测出来比 S3 快 10 倍,上线后崩了。
常见错误现象:
- 第一次上传很快,后续变慢 → NTFS 元数据缓存失效,实际是磁盘寻道暴露了
- CI 测出本地比 S3 慢 → CI 机器没装杀软,但生产服务器开了 Windows Defender 实时扫描
.tmp文件 - 用
File.WriteAllText测小文件 → 它内部用了StreamWriter+ UTF8 BOM,和 S3 的裸字节上传语义不一致
实操建议:
- 本地测试强制绕过系统缓存:
FileOptions.WriteThrough | FileOptions.NoBuffering(注意:需对齐 512 字节) - 关掉 OneDrive/Backup 同步目标目录,测试前运行
fsutil behavior set disablelastaccess 1关 LastAccessTime 更新 - 统一用
Stream接口,本地走new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough)
怎么让 S3 测试不被 AWS 限流打乱节奏
AWS 对未配置的客户端默认限流(比如 PutObject 限制 5 QPS),一旦触发 ThrottlingException,你的耗时直线上升,根本没法和本地比。
实操建议:
- 显式配置
AmazonS3Config:MaxErrorRetry = 0(防重试)、Timeout = TimeSpan.FromSeconds(30)(防卡死)、UseHttp = true(禁用 HTTPS 握手开销,仅限内网测试) - 并发控制必须由你代码管,别依赖 SDK 自动并发 —— 用
SemaphoreSlim限 10 路并发,比 SDK 默认的 50 更可控 - 提前预热连接池:测试开始前发 3 个空
HeadObject请求,确保 HTTP/1.1 连接复用已建立 - 检查响应头里的
x-amz-id-2和x-amz-request-id,如果一批请求里这两个值重复率高,说明被限流了
真正难的不是写两套代码,而是让它们在同等约束下暴露真实瓶颈——磁盘队列深度、TCP 拥塞窗口、TLS 握手延迟,这些底层差异不会自己说话,得靠你掐掉干扰项才能听见。








