【发布时间】:2014-10-24 09:30:17
【问题描述】:
对于蒙特卡洛积分过程,我需要从中提取 很多 个随机样本 具有 N 个桶的直方图,其中 N 是任意的(即不是 2 的幂),但 在计算过程中完全没有变化。
很多,我的意思是大约 10^10、100 亿,几乎任何 面对大量的 样本)。
我有一个非常快速的统一伪随机数生成器可供我使用 通常产生无符号的 64 位整数(讨论中的所有整数 以下未签名)。
提取样本的幼稚方法:histogram[ prng() % histogram.size() ]
天真的方法是非常慢:模运算使用整数除法(IDIV)
这是非常昂贵的编译器,不知道histogram.size() 的价值
在编译时,不能达到它通常的魔力(即http://www.azillionmonkeys.com/qed/adiv.html)
事实上,我的大部分计算时间都花在了提取那个该死的模数上。
稍微不那么天真的方式:我使用有能力的 libdivide (http://libdivide.com/) 实现非常快的“除以编译时未知的常数”。
这给了我一个很好的胜利(25% 左右),但我有一种唠叨的感觉,我可以做到 更好,原因如下:
第一直觉:libdivide 计算除法。我需要的是模数,然后到达那里 我必须做一个额外的 mult 和一个 sub :
mod = dividend - divisor*(uint64_t)(dividend/divisor)。我怀疑那里可能会有一个小胜利,使用 libdivide-type 直接产生模数的技术。第二个直觉:我实际上对模本身不感兴趣。我真正想要的是 有效地产生一个均匀分布的整数值,保证严格小于N。
模数是一种相当标准的方法,因为它有两个属性:
A) 如果
prng()是,则mod(prng(), N)保证均匀分布B)
mod(prgn(), N)保证属于 [0,N[
但是模是/做更多的只是满足上面的两个约束,事实上 它可能做的工作太多了。
所有需要的是一个函数,any 函数遵循约束 A) 和 B) 并且 快速。
所以,介绍很长,但这里有两个问题:
是否有与 libdivide 等效的方法直接计算整数模数?
-
是否有一些整数 X 和 N 的函数 F(X, N) 服从以下两个约束:
- 如果 X 是均匀分布的随机变量,则 F(X,N)也是不均匀分布
- F(X, N) 保证在 [0, N[
(PS : 我知道如果 N 很小,我不需要计算出所有的 64 位 PRNG。事实上,我已经这样做了。但就像我说的,即使是优化 与必须计算模数的巨大脂肪损失相比,这是一个小胜利。
编辑:prng() % N 确实不是完全均匀分布的。但是对于足够大的 N,我认为这不是什么大问题(或者是吗?)
编辑 2:prng() % N 确实可能分布非常糟糕。我从来没有意识到它会变得多么糟糕。哎哟。我找到了一篇很好的文章:http://ericlippert.com/2013/12/16/how-much-bias-is-introduced-by-the-remainder-technique
【问题讨论】:
-
(1) - 在大多数平台上,余数是“免费”计算的,是硬件级别除法的结果。 (2) - 取模和除法都不会给你一个公正的结果。
-
"A) mod(prng(), N) 保证均匀分布,如果 prng() is" 仅当
N均分M时才为真,其中prng()返回数字统一在[0, M[。 -
你试过
std::uniform_int_distribution吗? -
“编辑:prng() % N 确实不是完全均匀分布的。但是对于足够大的 N,我认为这不是什么大问题(或者是吗?)”它实际上变得更糟
N变大(即小 N 更好),例如如果prng()均匀地生成 0, 1, 2, ..., 15,则prng() % 4与3有一个小的偏差(3出现 20% 的时间,0、1、2 出现 26%) , 但prng() % 10对 0、1、...、5 有很大的偏差(它们的出现频率是 6、7、8、9 的 2 倍)。 -
你可以试试
histogram[ (int)(prng() * (HISTOGRAM_SIZE / (PRNG_MAX + 1.0))) ]。每个直方图预计算一次常数。这编译为一个浮点乘法和一个整数转换。使用 MMX 或 GPU 实现一次做多个。但我同意@OliCharlesworth 的观点,如果你用随机直方图访问来炸毁缓存,这是一个巨大的成本。
标签: c++ performance algorithm random random-sample