性能测试中,生成随机文件内容看似简单,但实际做对并不容易。很多开发者直接用rand(),结果数据分布不均,甚至每次运行结果都一样,导致测试结果完全失真。那么,正确的做法是什么?核心在于用对工具、用对方法,让生成过程本身不成为瓶颈。
用 std::random_device 和 std::mt19937 生成高质量随机字节
性能测试要的是可复现、高吞吐、低开销的随机数据,不是密码学安全级别——搞清楚这个区别,后面的选择就顺理成章了。直接用rand(),不仅分布不均、周期短,还可能因未调用srand()导致每次运行都生成相同序列,这会让测试结果完全失去参考价值。
正确的组合是std::random_device(用于种子)和std::mt19937(Mersenne Twister,快且均匀):
std::random_device rd; std::mt19937 gen(rd()); // 避免用 time(0) 做种子 std::uniform_int_distributiondist(0, 255);
std::random_device在多数平台(Linux / Windows MSVC / Clang)会读取系统熵源,比time(0)可靠得多- 用
uint8_t分布而非int,避免高位零填充导致写入时出现大量0x00字节(影响I/O模式判断) - 千万别在循环里反复构造
gen或dist,这些对象开销不小
批量生成比逐字节写入快10倍以上
考虑一个1GB的文件:如果每字节调用一次dist(gen)再写入磁盘,write()系统调用次数高达10⁹次,内核上下文切换开销会压倒一切。必须用缓冲区批量处理。
- 分配一块64KB~1MB的
std::vector缓冲区(太小没收益,太大易触发NUMA问题) - 用
std::generate填充整块缓冲区:std::generate(buf.begin(), buf.end(), [&]{ return dist(gen); }); - 用
write()或fwrite()一次性刷出,避免<<流操作(带格式化开销) - Linux下可加
O_DIRECT标志跳过页缓存(需对齐内存和文件偏移),但只在测裸设备或绕过缓存时才启用
生成结构化伪随机内容(如JSON行、CSV)要控制字段长度和分隔符
纯二进制随机数据无法验证解析器行为,真实性能测试常需要“看起来像数据”的内容。例如生成100万行JSON,每行包含"id"、"name"、"ts"字段。
这里有几个要点:
- 不要用
std::string拼接每行:堆分配+复制开销巨大;改用预分配缓冲区+snprintf或std::format(C++20)写入固定位置 - 名字字段可用随机长度(3–12字节)+随机ASCII字母填充,避免全空或超长字符串干扰测试焦点
- 时间戳建议用递增base + 小范围随机偏移(如
base_ts + dist_small(gen) % 1000),保证单调性便于后续排序验证 - 换行符必须显式写入
\n,Windows下若用\r\n会多占1字节/行,影响总大小精度
注意文件系统和libc的实际限制
生成几十GB文件时,std::ofstream的默认行为可能让你掉坑里:它不检查磁盘空间,也不处理ENOSPC错误,写失败后failbit被设但程序照常退出。
- 每次
write()后检查返回值:if (written != expected) { perror("write"); exit(1); } - 大文件建议用
posix_fallocate()(Linux)或SetFileInformationByHandle()(Windows)预先分配空间,避免碎片和写时扩展延迟 - glibc的
fwrite在缓冲区满时自动flush,但musl或某些嵌入式libc不一定;显式调用fflush()更稳妥 - 生成完成后务必调用
fsync()——否则你测的其实是page cache写入速度,不是磁盘真实吞吐
真正难的从来不是“怎么生成随机数”,而是让生成过程不成为性能瓶颈本身,以及确保生成的数据能准确反映你要测的那个环节的真实压力。