在使用this question时,我发现了一些我无法解释的关于np.log2
,np.log
和np.log10
的相对表现的内容:
In [1]: %%timeit x = np.random.rand(100000)
....: np.log2(x)
....:
1000 loops, best of 3: 1.31 ms per loop
In [2]: %%timeit x = np.random.rand(100000)
np.log(x)
....:
100 loops, best of 3: 3.64 ms per loop
In [3]: %%timeit x = np.random.rand(100000)
np.log10(x)
....:
100 loops, best of 3: 3.93 ms per loop
np.log2
比np.log
和np.log10
快3倍。也许更直观的是,计算 ln(x + 1)的np.log1p(x)
与np.log2
相同:
In [4]: %%timeit x = np.random.rand(100000)
np.log1p(x)
....:
1000 loops, best of 3: 1.46 ms per loop
我在numpy v1.10.1和v1.8.2中获得了几乎相同的时间。
是否有关于运行时性能差异的直观解释?
答案 0 :(得分:8)
我发现你的问题非常有趣,所以我花了几个小时做了一些研究;我认为我找到了性能差异的解释,因为它适用于整数(谢谢Matteo Italia注意) - 目前还不清楚这种推理如何扩展到浮点数:< / p>
计算机使用基数2 - 根据参考文献中链接的文章,log2的计算是4个处理器周期过程 - log10的计算需要将log2(val)乘以1 / log2(10),这又增加了5个周期。
查找log2属于finding the index of the least significant bit of a value。 (视频大约在第23分钟开始)。
有点骇客:Find integer log base 10 of an integer
有点骇客:Find the log base 2 of an N-bit integer in O(lg(N))
通过首先使用其中之一来计算整数对数基数10 以上技术用于查找日志库2.通过关系 log10(v)= log2(v)/ log2(10),我们需要乘以1 / log2(10), 大约是1233/4096,或1233,然后是右移 12.需要添加一个,因为IntegerLogBase2向下舍入。最后,因为值t只是可以关闭的近似值 一,通过减去v&lt;的结果找到确切的值。 PowersOf10 [T]。
此方法比IntegerLogBase2多运行6次。它可能是 通过修改日志加快(在具有快速内存访问的机器上) 基本2表查找方法,以便条目保持是什么 为t计算(即pre-add,-mulitply和-shift)。这样做 将需要总共只有9个操作才能找到日志库10, 假设使用了4个表(每个字节对应一个表)。
值得注意的是:使用DeBruijn序列查找&amp;用于计算此MIT video: Lec 2 | MIT 6.172 Performance Engineering of Software Systems, Fall 2010 中的log2的位移技术(第36分钟以后的视频)。
请注意,此StackOverflow帖子演示了a method to make efficient log2 calculations truly cross platform with C++
警告:我没有检查过numpy源代码来验证它确实实现了类似的技术,但是它确实没有。事实上,根据OP的帖子中的评论,Fermion Portal已经检查过:
实际上numpy使用来自glibc的math.h,你会看到相同的区别 在C / C ++中如果使用math.h / cmath.h。你可以找到很好的评论 两个函数的源代码,例如 ic.unicamp.br/~islene/2s2008-mo806/libc/sysdeps/ieee754/dbl-64/…和 ic.unicamp.br/~islene/2s2008-mo806/libc/sysdeps/ieee754/dbl-64/… - Fermion Portal [9]
答案 1 :(得分:6)
这只是一个注释,但比评论更长。显然这与您的特定安装有关:
import numpy as np
import numexpr as ne
x = np.random.rand(100000)
我从conda获得了与numpy 1.10相同的时序,并使用icc编译了一个版本:
%timeit np.log2(x)
1000 loops, best of 3: 1.24 ms per loop
%timeit np.log(x)
1000 loops, best of 3: 1.28 ms per loop
我认为抓住MKL VML软件包可能会有所帮助,但看起来不是这样:
%timeit ne.evaluate('log(x)')
1000 loops, best of 3: 218 µs per loop
看起来你的numpy安装正从两个不同的地方抓取它的log / log2实现,这很奇怪。
答案 2 :(得分:4)
免责声明:我既不可信,也不是官方来源。
我几乎可以肯定,任何基本e函数的日志实现都可以像log2函数一样快,因为要将一个函数转换为另一个函数,你需要用一个常量来划分。这当然假设单个除法运算只是其他计算的一小部分;在准确的对数实现中是正确的。
在大多数情况下,numpy使用math.h
中的glibc
,如果您使用math.h
/ cmath.h
,您会看到C / C ++中的相同差异。在评论中,有些人观察np.log
和np.log2
的速度相同;我怀疑这可能来自不同的构建/平台。
您可以在e_log.c
和{{e_log2.c
中的文件e_logf.c
,e_log2f.c
,dbl-64/
,flt-32/
中找到两个功能的评论很好的源代码1}} this GitHub repo的子目录。
对于双精度,在glibc
中,log
函数实现了一个完全不同的算法(与log2
相比)来自IBM的~2001,它包含在{{1}中}} 图书馆。而libultim
来自Sun Microsystems,从1993年开始。只需查看代码,就可以看到正在实现两种不同的近似值。相比之下,对于单精度,log2
和log
除了在log2
情况下除ln2
之外都是相同的,因此速度相同。
有关未来log2
中包含的基础算法,替代方案和讨论的更多背景信息,请参阅here。
答案 3 :(得分:2)
(应该是评论,但会太长了......)
为了使这更有趣,2018年在Windows 10 64位机器上,结果是相反的。
默认Anaconda
Python 3.6.3 |Anaconda, Inc.| (default, Oct 15 2017, 03:27:45) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import numpy as np; np.random.seed(0); x = np.random.rand(100000)
...: %timeit np.log2(x)
...: %timeit np.log1p(x)
...: %timeit np.log(x)
...: %timeit np.log10(x)
...:
1.48 ms ± 18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.33 ms ± 36.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
840 µs ± 7.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
894 µs ± 2.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
<强> Intel Python 强>
Python 3.6.3 |Intel Corporation| (default, Oct 17 2017, 23:26:12) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import numpy as np; np.random.seed(0); x = np.random.rand(100000)
...: %timeit np.log2(x)
...: %timeit np.log1p(x)
...: %timeit np.log(x)
...: %timeit np.log10(x)
...:
1.01 ms ± 2.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
236 µs ± 6.08 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
161 µs ± 1.77 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
171 µs ± 1.34 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)