Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/331.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Numpy:除法0.5有什么特别之处?_Python_Performance_Numpy - Fatal编程技术网

Python Numpy:除法0.5有什么特别之处?

Python Numpy:除法0.5有什么特别之处?,python,performance,numpy,Python,Performance,Numpy,@Dunes的这一部分指出,由于流水线运算,浮点乘法和除法(几乎)没有区别。然而,根据我在其他语言方面的经验,我认为分工会慢一些 我的小测试如下所示: A=np.random.rand(size) command(A) 对于不同的命令和size=1e8我在我的机器上获得以下时间: Command: Time[in sec]: A/=0.5 2.88435101509 A/=0.51 5.22591209412 A*=2.0 1.1831600666 A*2.0

@Dunes的这一部分指出,由于流水线运算,浮点乘法和除法(几乎)没有区别。然而,根据我在其他语言方面的经验,我认为分工会慢一些

我的小测试如下所示:

A=np.random.rand(size)
command(A)
对于不同的命令和
size=1e8
我在我的机器上获得以下时间:

Command:    Time[in sec]:
A/=0.5      2.88435101509
A/=0.51     5.22591209412
A*=2.0      1.1831600666
A*2.0       3.44263911247  //not in-place, more cache misses?
A+=A        1.2827270031
最有趣的部分:除以
0.5
的速度几乎是除以
0.51
的两倍。可以假设,这是由于一些智能优化,例如用
A+A
替换除法。然而,
A*2
A+A
的计时距离太远,无法支持这一说法

一般来说,使用值
(1/2)^n
除以浮点的速度更快:

Size: 1e8
    Command:    Time[in sec]:
    A/=0.5      2.85750007629
    A/=0.25     2.91607499123
    A/=0.125    2.89376401901
    A/=2.0      2.84901714325
    A/=4.0      2.84493684769
    A/=3.0      5.00480890274
    A/=0.75     5.0354950428
    A/=0.51     5.05687212944
如果我们看一下
size=1e4
,它会变得更有趣:

Command:    1e4*Time[in sec]:
A/=0.5      3.37723994255
A/=0.51     3.42854404449
A*=2.0      1.1587908268
A*2.0       1.19793796539
A+=A        1.11329007149
现在,按
.5
除法和按
.51
除法没有区别

我在不同的numpy版本和不同的机器上试用过。在某些机器上(例如Intel Xeon E5-2620)可以看到这种效果,但在其他一些机器上看不到这种效果-这并不取决于numpy版本

通过@Ralph Versteegen的脚本(参见他的精彩答案!),我得到了以下结果:

  • i5-2620的计时(Haswell,2x6内核,但非常旧的numpy版本,不使用SIMD):

  • i7-5500U(Broadwell,2芯,numpy 1.11.2)的计时:

问题是:对于某些处理器,如果阵列大小较大(>10^6),那么与除以
0.5
相比,除以
0.51
的成本更高的原因是什么

@nneonneo的回答指出,对于某些英特尔处理器,当除以二的幂时,会有一个优化,但这并不能解释为什么我们只能看到它对大型阵列的好处


最初的问题是“如何解释这些不同的行为(按
0.5划分与按
0.51划分)

这里还有我的原始测试脚本,它产生了时间安排:

import numpy as np
import timeit

def timeit_command( command, rep):
    print "\t"+command+"\t\t", min(timeit.repeat("for i in xrange(%d):"
        %rep+command, "from __main__ import A", number=7))    

sizes=[1e8,  1e4]
reps=[1,  1e4]
commands=["A/=0.5", "A/=0.51", "A*=2.2", "A*=2.0", "A*2.2", "A*2.0",
          "A+=A", "A+A"]

for size, rep in zip(sizes, reps):
    A=np.random.rand(size)
    print "Size:",size
    for command in commands:
        timeit_command(command, rep)

起初我怀疑numpy正在调用BLAS,但至少在我的机器(python 2.7.13、numpy 1.11.2、OpenBLAS)上,它没有调用BLAS,通过gdb快速检查发现:

> gdb --args python timing.py
...
Size: 100000000.0
^C
Thread 1 "python" received signal SIGINT, Interrupt.
sse2_binary_scalar2_divide_DOUBLE (op=0x7fffb3aee010, ip1=0x7fffb3aee010, ip2=0x6fe2c0, n=100000000)
    at numpy/core/src/umath/simd.inc.src:491
491 numpy/core/src/umath/simd.inc.src: No such file or directory.
(gdb) disass
   ...
   0x00007fffe6ea6228 <+392>:   movapd (%rsi,%rax,8),%xmm0
   0x00007fffe6ea622d <+397>:   divpd  %xmm1,%xmm0
=> 0x00007fffe6ea6231 <+401>:   movapd %xmm0,(%rdi,%rax,8)
   ...
(gdb) p $xmm1
$1 = {..., v2_double = {0.5, 0.5}, ...}

英特尔CPU在除以二的幂时有特殊的优化。例如,请参见其中的说明

FDIV延迟取决于控制字中指定的精度:64位精度 给出延迟38,53位精度给出延迟32,24位精度给出延迟18。2的幂除法需要9个时钟

尽管这适用于FDIV而不是DIVPD(正如@RalphVersteegen的回答所述),但如果DIVPD也没有实现这种优化,那将是非常令人惊讶的



分裂通常是一件非常缓慢的事情。然而,除以二的幂只是一个指数移动,尾数通常不需要改变。这使得操作非常快。此外,在浮点表示法中很容易检测到二的幂,因为尾数都是零(带一个隐式的前导1),所以这种优化既易于测试,又易于实现。

您的基准是什么?算术速度还是解释器效率?@Yves我的目标是对算术速度进行基准测试,但我不确定我的实际基准测试是什么。我无法重现0.5和0.51之间的除法差异。在IPython中使用
%timeit
魔术似乎花费了相同的时间,不管数组大小如何。@Laleh这是整数值,浮点值也是如此吗?@ajcr在另一台具有较新硬件的机器上,我也看不到
0.5
0.51
之间的区别:(你是对的,这是使用的同一个代码,所以差异一定来自硬件。仍然有很多事情我不明白,例如,为什么0.5和0.51对于较小的尺寸没有区别?老实说,有很多东西我也不明白,所以我重新检查了它,并重写了我的答案的下半部分。我是我了解了一些事情,但不幸的是,我仍然不了解所有内容。这是一个很好的答案!我用2核机器上的测量更新了我的问题,并且
.51
.5
之间没有区别。但是,在6核沙桥上(你的也有6核)我在你的绘图中看到了类似的行为。每个
divpd
操作有额外的2
movapd
、1
add
、1
cmp
jb
操作,但它们能解释agner表的巨大差异吗?这是我的第一个想法。发现不错。
import numpy as np
import timeit
import matplotlib.pyplot as plt

CPUHz = 3.3e9
divpd_cycles = 4.5
L2cachesize = 2*2**20
L3cachesize = 8*2**20

def timeit_command(command, pieces, size):
    return min(timeit.repeat("for i in xrange(%d): %s" % (pieces, command),
                             "import numpy; A = numpy.random.rand(%d)" % size, number = 6))

def run():
    totaliterations = 1e7

    commands=["A/=0.5", "A/=0.51", "A/0.5", "A*=2.0", "A*2.0", "A+=2.0"]
    styles=['-', '-', '--', '-', '--', '-']

    def draw_graph(command, style, compute_overhead = False):
        sizes = []
        y = []
        for pieces in np.logspace(0, 5, 11):
            size = int(totaliterations / pieces)
            sizes.append(size * 8)  # 8 bytes per double
            time = timeit_command(command, pieces, (4 if compute_overhead else size))
            # Divide by 2 because SSE instructions process two doubles each
            cycles = time * CPUHz / (size * pieces / 2)
            y.append(cycles)
        if compute_overhead:
            command = "numpy overhead"
        plt.semilogx(sizes, y, style, label = command, linewidth = 2, basex = 10)

    plt.figure()
    for command, style in zip(commands, styles):
        print command
        draw_graph(command, style)
    # Plot overhead
    draw_graph("A+=1.0", '-', compute_overhead=True)

    plt.legend(loc = 'best', prop = {'size':9}, handlelength = 3)
    plt.xlabel('Array size in bytes')
    plt.ylabel('CPU cycles per SSE instruction')

    # Draw vertical and horizontal lines
    ymin, ymax = plt.ylim()
    plt.vlines(L2cachesize, ymin, ymax, color = 'orange', linewidth = 2)
    plt.vlines(L3cachesize, ymin, ymax, color = 'red', linewidth = 2)
    xmin, xmax = plt.xlim()
    plt.hlines(divpd_cycles, xmin, xmax, color = 'blue', linewidth = 2)