3 个稳定版本

1.0.2 2022年6月16日

#139 in 性能分析

MIT/Apache

19KB
104

fast-bernoulli

使用均匀概率进行高效抽样

在收集有关程序行为的统计数据时,我们可能会观察到发生频率很高的事件(例如,函数调用或内存分配),并且我们可能会收集一些生产成本较高的信息(例如,调用栈)。对所有事件进行采样可能会对程序的性能产生重大影响。

为什么不只采样每 N 个事件呢?这种技术被称为“系统抽样”;它简单且高效,如果我们想象一个无模式的事件流,那么它是可以接受的。但是,如果我们正在采样分配,并且程序恰好有一个循环,每次迭代都恰好进行 N 次分配呢?你将每次都会采样相同的分配;循环的其余部分将完全从你的测量中消失!更一般地说,如果每个迭代进行 M 次分配,并且 MN 有任何公约数,大多数分配位置将永远不会被采样。如果它们都是偶数,比如,奇数分配将从你的结果中消失。

理想情况下,我们希望每个事件都有一些概率 P 被采样,与邻居无关,与它在序列中的位置也无关。这被称为 "伯努利抽样",并且不会受到上述任何问题的困扰。

伯努利抽样的一个缺点是,你无法确切知道你会得到多少样本:技术上,你可能会一个都不采样,或者全部都采样。但是,如果事件的数目 N 很大,这些结果不太可能;你通常可以期望大约 P * N 的事件被采样。

伯努利抽样的另一个缺点是,你必须为每个事件生成一个随机数,这可能会很慢。

<显著暂停>

但不是这个箱子的!

FastBernoulli 允许您进行真正的伯努利抽样,但只有在决定抽样事件时才会生成新的随机数,而不是在每次试验时。当决定不抽样时,对 FastBernoulli::trial 的调用只是减少计数器并比较它与零。因此,您的抽样概率越低,FastBernoulli 施加的额外开销就越少。

最后,对 01 的概率进行了高效的处理。(在两种情况下都不需要生成任何随机数。)

示例

use fast_bernoulli::FastBernoulli;
use rand::Rng;

// Get the thread-local random number generator.
let mut rng = rand::thread_rng();

// Create a `FastBernoulli` instance that samples events with probability 1/20.
let mut bernoulli = FastBernoulli::new(0.05, &mut rng);

// Each time your event occurs, perform a Bernoulli trail to determine whether
// you should sample the event or not.
let on_my_event = || {
    if bernoulli.trial(&mut rng) {
        // Record the sample...
    }
};

灵感

这个包使用与 Jim Blandy 在 Firefox 中的 FastBernoulliTrial 相同的技术。这个实现并不是直接从 C++ 转录到 Rust,然而我确实复制(并略作编辑)了一些原始文档和注释(例如,本 README 和 crate 级别的文档的大部分内容)。

依赖关系

~310KB