CppCon 2016 学习:I Just Wanted a Random Integer
你想要一个随机整数,用于模拟随机大小的DNA读取片段(reads),希望覆盖不同长度范围,也能测试边界情况。
代码部分是:
#include <cstdlib>
auto r = std::rand() % 100;
它生成一个0到99之间的随机整数,表示随机读取的长度。
简单来说:
- 你用
std::rand()
生成伪随机数。 % 100
限制范围在0~99。- 这样就得到了一个随机的DNA片段大小。
如果你想更好的随机数(比如更均匀、更现代的C++随机),可以用<random>
:
#include <random>
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(0, 99);
int r = dist(gen);
rand()
和 srand()
在 Linux C 库里的实现细节和注意事项:
- Linux 中的
rand()
和srand()
实际上是用跟random()
和srandom()
相同的随机数生成器。 - 因此,低位比高位随机性差的说法在 Linux 上不适用,低位和高位都一样随机。
- 但是,在旧版
rand()
实现或其他系统中,rand()
的低位随机性可能很差。 - 因此,如果你的应用需要良好的跨平台随机性,不建议用
rand()
,而是应该用random()
(或更现代的随机数生成器,比如 C++11 的<random>
)。
总结就是: rand()
低位不随机的问题主要是旧系统和非 Linux 的实现。- 在 Linux 上,
rand()
表现好一些,但为可移植和更好随机性,还是推荐用random()
或<random>
。
如果你想要更稳定、好用的随机数生成方式,尤其是在 C++,推荐用<random>
库。
在写单元测试时,想要一个“随便的随机整数”,看似用 std::rand()
和 %
操作就够了,但其实这样做很容易出问题,尤其是你想要“均匀覆盖所有边界条件”的时候。
std::rand()
+%
其实不够随机,会导致随机数分布不均匀,某些数字出现概率偏低或偏高。<random>
库(C++11及以后)虽然看起来复杂,但其实是更科学、更可靠的随机数生成方式。- 推荐使用 Mersenne Twister (
std::mt19937
) +std::uniform_int_distribution
+std::random_device
作为种子,这样既有高质量的伪随机数,又能基于真实熵来初始化。 std::random_device
提供真随机的熵,但它的熵资源是有限的,有时会阻塞等待更多熵。- 要注意,生成高质量随机数时,熵是关键,但计算机本质上是伪随机,需要熵源来“真随机”初始化。
总结就是:
你单元测试要用“随机数”,不能随便用rand()%N
,而要用更严谨的<random>
方案,才能更好地覆盖边界和确保随机性。
用 C++ <random>
生成随机整数时的细节和性能考量:
核心内容总结:
std::random_device
- 用于获取真随机熵,作为伪随机数引擎的种子。
- 可能很慢,有时甚至会阻塞(因为熵资源有限)。
- 可能会抛异常,且多线程环境行为不明。
- 具体实现依赖硬件和操作系统,比如Linux上常用
/dev/urandom
。
std::mt19937
(Mersenne Twister)- 伪随机数生成器,速度快且质量较高。
- 是确定性引擎,给定种子后输出固定序列。
- 体积大(约5000字节栈空间),初始化较慢(百万次初始化需要十几秒)。
- 每次函数调用时重新创建会很慢!
- 性能测试对比
- 生成10亿随机数:
std::random_device
用时约44秒(慢且可能阻塞)。std::mt19937
用时约3.6秒(快得多)。
- 生成10亿随机数:
- 最佳实践建议
- 不要每次用
std::mt19937
时都重新初始化引擎,建议将其作为静态变量或者线程局部变量来重用,比如:static std::mt19937 g(std::random_device{}()); auto rn = g();
- 这样既避免了重复初始化开销,也保证了伪随机数生成速度。
- 不要每次用
C++生成随机整数的正确做法和背后的算法进行讲解,重点如下:
1. 随机整数生成的常见准则(Guidelines)
- 用
std::random_device
来做种子(seed):确保伪随机数生成器有较好的随机种子。 - 不要把
std::mt19937
(Mersenne Twister)放在栈上频繁构造:构造开销大,最好是静态变量或线程局部变量(thread_local
)。 - 如果一定放在栈上,要小心构造开销。
std::minstd_rand
比较快,但周期更短。
2. 常用随机数生成器性能对比
std::random_device
速度很慢(约44秒)。std::mt19937
中等速度(约3.6秒),周期极长。std::minstd_rand
更快(约4.7秒),周期短。
3. Mersenne Twister 背景和定义
- 命名来自 Marin Mersenne(1588-1648),梅森素数的形式是 M n = 2 n − 1 M_n = 2^n - 1 Mn=2n−1,其中 n n n 是素数。
- 例如: M 3 = 7 M_3 = 7 M3=7, M 7 = 127 M_7 = 127 M7=127, 最大有名的是 M 74 , 207 , 281 M_{74,207,281} M74,207,281。
std::mt19937
的周期就是一个梅森素数 M 19937 M_{19937} M19937。- 这是一个维度为623的伪随机数生成器,详细参数在源码里定义(比如状态大小、位移操作等)。
4. 结论
- 使用
std::mt19937
是因为它周期非常长且质量很好。 - 但在性能敏感的场景,可以考虑
std::minstd_rand
作为替代。 - 生成器的使用要注意对象生命周期管理,避免重复构造带来的开销。
这段内容主要讲了C++随机数生成的一些进阶话题,尤其是关于性能优化和替代生成器的讨论。帮你整理重点:
1. PCG 随机数生成器介绍
- PCG (Permuted Congruential Generator) 是 Melissa O’Neill 设计的随机数生成器。
- 优点:
- 体积更小(16字节),比
std::mt19937
省空间。 - 和 C++
<random>
库兼容,可以无缝替代。 - 性能比
std::mt19937
快很多(示例中1.5秒 vs 3.6秒)。
- 体积更小(16字节),比
- 缺点:
- 目前不在标准库中,社区使用经验较少。
- 参考资源:
- PCG 官网
- GitHub 地址:
https://github.com/imneme/pcg-cpp
2. std::uniform_int_distribution 的构造成本
std::uniform_int_distribution
是用于生成均匀整数分布的模板类。- 在循环内部构造和在循环外部构造性能差异极大:
- 外部构造(只构造一次):生成1,000,000,000个数大约 23.4秒。
- 内部构造(每次调用都构造):生成同样数量大约 5.1秒(这里好像反了,推测是复制时的编译优化影响)。
- 具体性能依赖于编译器和优化级别,但通常建议避免在循环内重复构造分布对象。
3. 随机数生成性能总结
- 通过静态变量(
static std::random_device
和static std::mt19937
)维护生成器实例,减少构造开销。 - 如果需要高性能,考虑用PCG替代MT19937。
std::uniform_int_distribution
构造开销也不能忽视,尽量循环外构造。
4. 代码示例总结
static std::random_device entropySource;
static std::mt19937 randGenerator(entropySource());
std::uniform_int_distribution<int> theIntDist(0, 99);
for (auto i = 0; i < 1'000'000'000; i++) {volatile auto r = theIntDist(randGenerator);
}
- 这是一个推荐的模式:生成器和分布都静态,只生成随机数时调用
operator()
。
你这段内容讲的是 C++ 标准库<random>
中std::uniform_int_distribution
生成均匀随机整数时,内部是如何“缩放”原始随机数到目标范围的细节,和对随机数生成器及分布器使用的建议总结。
代码片段说明(缩放机制)
const __uctype __urange = __uctype(__param.b()) - __uctype(__param.a());
__uctype __ret;
if (__urngrange > __urange) {// downscalingconst __uctype __uerange = __urange + 1; // 目标范围大小(包括边界)const __uctype __scaling = __urngrange / __uerange; // 源随机数范围除以目标范围大小,算出缩放系数const __uctype __past = __uerange * __scaling; // 缩放后实际范围do__ret = __uctype(__urng()) - __urngmin; // 从源随机数生成器拿数,减去最小值while (__ret >= __past); // 如果随机数超过缩放范围则重新取数,保证均匀__ret /= __scaling; // 缩放回目标范围
}
- 这段代码的作用是确保从底层随机数生成器的输出映射到指定的整数范围
[a, b]
时,能保持均匀分布。 - 通过“拒绝采样”(
while (__ret >= __past)
)来避免偏差。 - 这是
<random>
里实现uniform_int_distribution
的核心逻辑之一。
你的“指南”和总结
- Use your engineering judgment
任何方案都要结合具体项目和需求来判断。 <random>
is safe
标准库<random>
已经很安全、稳定,推荐优先使用。- PCG is fast, small and simple
PCG 随机数生成器小巧快速,是很好的替代方案。 - Combine PCG with
<random>
PCG 可以作为底层生成器,配合<random>
中的分布类用起来也非常方便。 - Always measure. Always!
任何性能和正确性上的假设都要靠测量和测试来验证。
最后总结
- 用
std::random_device
来产生随机种子。 - 选择
std::mt19937
或 PCG 作为伪随机数生成器。 - 分布类(
uniform_int_distribution
等)构造和使用开销不大,不必担心性能问题。 - C++17 引入了更方便的采样工具,如
std::sample
。 - 性能和行为要靠基准测试来决定,理论和实际可能不同。