【发布时间】:2016-06-07 12:54:03
【问题描述】:
我在需要计算组合的 Node.js 服务器上有一些性能敏感代码。从this SO answer,我用这个简单的递归函数来计算n选择k:
function choose(n, k) {
if (k === 0) return 1;
return (n * choose(n-1, k-1)) / k;
}
那么既然我们都知道迭代几乎总是比递归快,我就根据multiplicative formula写了这个函数:
function choosei(n,k){
var result = 1;
for(var i=1; i <= k; i++){
result *= (n+1-i)/i;
}
return result;
}
我在我的机器上运行了几个benchmarks。以下是其中之一的结果:
Recursive x 178,836 ops/sec ±7.03% (60 runs sampled)
Iterative x 550,284 ops/sec ±5.10% (51 runs sampled)
Fastest is Iterative
结果一致表明,迭代方法确实比 Node.js 中的递归方法快大约 3 到 4 倍(至少在我的机器上)。
这可能已经足够满足我的需要了,但是有什么方法可以让它更快?我的代码必须非常频繁地调用这个函数,有时n 和k 的值相当大,所以越快越好。
编辑
在使用 le_m 和 Mike 的解决方案运行了更多测试后,结果证明虽然两者都比我提出的迭代方法快得多,但使用 Pascal 三角形的 Mike 方法似乎比 le_m 的日志表方法快一些。
Recursive x 189,036 ops/sec ±8.83% (58 runs sampled)
Iterative x 538,655 ops/sec ±6.08% (51 runs sampled)
LogLUT x 14,048,513 ops/sec ±9.03% (50 runs sampled)
PascalsLUT x 26,538,429 ops/sec ±5.83% (62 runs sampled)
Fastest is PascalsLUT
在我的测试中,对数查找方法比迭代方法快大约 26-28 倍,使用帕斯卡三角形的方法比对数查找方法快大约 1.3 到 1.8 倍。
请注意,我遵循了 le_m 的建议,即使用 mathjs 预先计算更高精度的对数,然后将它们转换回常规 JavaScript Numbers(始终为 double-precision 64 bit floats)。
【问题讨论】:
-
如果您不关心空间,记忆化可能是最快的选择。
-
来自您链接的答案的评论和wikipedia page:
if (k > n/2) return choose(n, n-k);- 当n和k都很大时,这将有所帮助,但额外的分支可能会整体减慢执行。 -
@Godfather @NanoWizard 和如果你没有空间来 momoize 选择结果,你可以通过 momoizing 阶乘获得良好的性能提升。您甚至可以使用动态编程技术来计算阶乘
fact(n) = max_known_fact_value(n) * [i for evey int for max_known_fact_int(n) to n],从而节省大量时间 -
是的@gbtimmon 那会很棒!
-
不是出于实际目的。使用 UINT16,您可以在 380 字节 + 数组开销中存储最多 n=19 的 LUT。 UINT32 允许在超过 2kb 的部分中最多 n=35。 UINT64 最多允许 n=68,现在我们已经正确地超越了“你真的相信你需要这么高的二项式系数吗?”值超过 18kb。如果他们已经致力于进行二项式计算,那么这些 malloc 没有人会关心他们。尤其是在 Node.js 中,与基本 Node 的内存占用相比,这些 LUT 微不足道。
标签: javascript node.js algorithm performance