2024年1月7日,星期日,下午5:46,John Jannotti < jannotti@gmail.com > 写道:
我很喜欢大数实现,所以我在浏览nat.go时发现mulRange
以一种令人惊讶的递归方式实现。在非基本情况下,mulRange(a, b)
返回mulrange(a, (a+b)/2) * mulRange(1+(a+b)/2, b)
(许多big.Int仪式被省略)。
这没问题,但我没有看到它比简单直接的for循环有什么优势。
z = z.setUint64(a)
for m := a + 1; m <= b; m++ {
z = z.mul(z, nat(nil).setUint64(m))
}
return z
实际上,我怀疑现有的代码运行速度更慢,分配了更多的内存。事实证明这是正确的。使用现有的单元测试作为基准进行快速基准测试,结果如下:
BenchmarkRecusiveMulRangeN-10 169417 6856 ns/op 9452 B/op 338 allocs/op
BenchmarkIterativeMulRangeN-10 265354 4269 ns/op 2505 B/op 196 allocs/op
我怀疑mulRange
不是任何人代码中的性能瓶颈!但是它被导出为int.MulRange
,所以我想它有一定的价值。鉴于for循环似乎更容易理解递归版本,也许值得提交一个PR?(如果是这样,我应该先创建一个问题吗?)
5条答案
按热度按时间yxyvkwin1#
应该可以通过预先分配接近正确数量的空间,然后使用
mulAddVWW
迭代计算乘积来实现 mulRange。s71maibg2#
由于
nat.mul
具有优化调用z.mulAddWW
的功能,当y只有一个单词时,以下分配仅执行一次(在64位平台上)。maxBits
将在类似mulRange(0, math.MaxUInt64)
的病态输入下溢出。这种溢出不会影响正确性(如果它能完成),它只会启动z
太小。当然,当它试图增长到足够大时,它会失败。基准:
产生
332nm8kg3#
Bakul Shah的评论bakul@iitbombay.org引导我调查了非常大的输入。
通过在递归的每一侧建立大值,Karatsuba用于较大的乘法运算,递归版本开始获胜。在
mulRange(1_000, 200_000)
上,它比单分配迭代版本快20多倍。我将编写一个混合版本,使用迭代方法处理较短的范围,这应该让我们两者兼得。
ivqmmu1c4#
参考:https://groups.google.com/g/golang-nuts/c/7kcFb41ARgM/m/5sSxZlKfAgAJ
这可以很容易地并行化,尽管可能不值得,除非用于特定的应用。
一些阶乘的背景和其他有趣的算法:http://www.luschny.de/math/factorial/FastFactorialFunctions.htm
Richard Fateman的CL实现:https://people.eecs.berkeley.edu/~fateman/papers/factorial.pdf
7ivaypg95#
我有一个简单的混合实现,如下所示:
不幸的是,截止值
> 2 * karatsubaThreshold
有点低。在一个基准测试中,a
和b
之间的范围越来越大,在截止值上方有一个略慢的窗口大小。因此,截止值应该更高。然而,我不确定将其硬编码到calibration_test.go
中是否值得。一个由手工调整确定的“魔术”截止值,如
maxWords > uint64(5*karatsubaThreshold/2)
或类似的东西,是否可以接受,还是你想看到更严格的内容?