为什么log2和log1p比log和log10快得多?

时间:2015-11-19 16:50:12

标签: python performance numpy glibc logarithm

在使用this question时,我发现了一些我无法解释的关于np.log2np.lognp.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.log2np.lognp.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中获得了几乎相同的时间。

是否有关于运行时性能差异的直观解释?

4 个答案:

答案 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.lognp.log2的速度相同;我怀疑这可能来自不同的构建/平台。

您可以在e_log.c和{{e_log2.c中的文件e_logf.ce_log2f.cdbl-64/flt-32/中找到两个功能的评论很好的源代码1}} this GitHub repo的子目录。

对于双精度,在glibc中,log函数实现了一个完全不同的算法(与log2相比)来自IBM的~2001,它包含在{{1}中}} 图书馆。而libultim来自Sun Microsystems,从1993年开始。只需查看代码,就可以看到正在实现两种不同的近似值。相比之下,对于单精度,log2log除了在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)