Python 对于纯numpy代码,使用numba的收益来自哪里?

Python 对于纯numpy代码,使用numba的收益来自哪里?,python,numpy,numba,Python,Numpy,Numba,我想了解在for循环中使用Numba来加速purenumpy代码时,收益来自哪里。是否有任何分析工具允许您查看jitted函数 演示代码(如下)只是使用非常基本的矩阵乘法为计算机提供工作。观察到的收益来自: 更快的循环 编译过程中被jit截获的numpy函数的重铸,或 使用jit减少开销,因为numpy通过包装函数将执行外包给低级库,如LINPACK %matplotlib内联 将numpy作为np导入 从numba导入jit 作为pd进口熊猫 #矩阵的维数 i=100 j=100 def pu

我想了解在for循环中使用Numba来加速pure
numpy
代码时,收益来自哪里。是否有任何分析工具允许您查看
jitted
函数

演示代码(如下)只是使用非常基本的矩阵乘法为计算机提供工作。观察到的收益来自:

  • 更快的
    循环
  • 编译过程中被
    jit
    截获的
    numpy
    函数的重铸,或
  • 使用
    jit
    减少开销,因为numpy通过包装函数将执行外包给低级库,如
    LINPACK
  • %matplotlib内联
    将numpy作为np导入
    从numba导入jit
    作为pd进口熊猫
    #矩阵的维数
    i=100
    j=100
    def pure_python(N,i,j):
    对于范围内的n(n):
    a=np.random.rand(i,j)
    b=np.random.rand(i,j)
    c=np.点(a,b)
    @jit(nopython=True)
    def jit_python(N,i,j):
    对于范围内的n(n):
    a=np.random.rand(i,j)
    b=np.random.rand(i,j)
    c=np.点(a,b)
    时间_python=[]
    时间(jit)=[]
    N=[1,1010050010002000]
    对于n中的n:
    time=%timeit-oq纯python(n,i,j)
    time\u python.append(time.average)
    time=%timeit-oq jit_python(n,i,j)
    time\u jit.append(time.average)
    数据帧({'pure_python':time_python'jit_python':time_jit},index=N)
    df.index.name='Iterations'
    df[[“pure_python”,“jit_python”]].plot()
    
    生成以下图表


    TL:DR随机循环和循环加速,但矩阵乘法除了较小的矩阵大小外,不会发生。在较小的矩阵/循环大小下,似乎存在可能与python开销有关的显著加速。在很大程度上,矩阵乘法开始占主导地位,而jit则没有什么帮助

    函数定义,为简单起见,使用平方矩阵

    from IPython.display import display
    import numpy as np
    from numba import jit
    import pandas as pd
    
    #Dimensions of Matrices
    N = 1000
    
    def py_rand(i, j):
        a = np.random.rand(i, j)
    
    jit_rand = jit(nopython=True)(py_rand)
    
    def py_matmul(a, b):
        c = np.dot(a, b)
    
    jit_matmul = jit(nopython=True)(py_matmul)
    
    def py_loop(N, val):
        count = 0
        for i in range(N):
            count += val     
    
    
    jit_loop = jit(nopython=True)(py_loop)      
    
    def pure_python(N,i,j):
        for n in range(N):
            a = np.random.rand(i,j)
            b = np.random.rand(i,j)
            c = np.dot(a,a)
    
    jit_func = jit(nopython=True)(pure_python)
    
    时间:

    df = pd.DataFrame(columns=['Func', 'jit', 'N', 'Time'])
    def meantime(f, *args, **kwargs):
        t = %timeit -oq -n5 f(*args, **kwargs)
        return t.average
    
    
    for N in [10, 100, 1000, 2000]:
        a = np.random.randn(N, N)
        b = np.random.randn(N, N)
    
        df = df.append({'Func': 'jit_rand', 'N': N, 'Time': meantime(jit_rand, N, N)}, ignore_index=True)
        df = df.append({'Func': 'py_rand', 'N': N, 'Time': meantime(py_rand, N, N)}, ignore_index=True)
    
        df = df.append({'Func': 'jit_matmul', 'N': N, 'Time': meantime(jit_matmul, a, b)}, ignore_index=True)
        df = df.append({'Func': 'py_matmul', 'N': N, 'Time': meantime(py_matmul, a, b)}, ignore_index=True)
    
        df = df.append({'Func': 'jit_loop', 'N': N, 'Time': meantime(jit_loop, N, 2.0)}, ignore_index=True)
        df = df.append({'Func': 'py_loop', 'N': N, 'Time': meantime(py_loop, N, 2.0)}, ignore_index=True)
    
        df = df.append({'Func': 'jit_func', 'N': N, 'Time': meantime(jit_func, 5, N, N)}, ignore_index=True)
        df = df.append({'Func': 'py_func', 'N': N, 'Time': meantime(pure_python, 5, N, N)}, ignore_index=True)
    
    df['jit'] = df['Func'].str.contains('jit')
    df['Func'] = df['Func'].apply(lambda s: s.split('_')[1])
    df.set_index('Func')
    display(df)
    
    结果:

        Func    jit     N   Time
    0   rand    True    10  1.030686e-06
    1   rand    False   10  1.115149e-05
    2   matmul  True    10  2.250371e-06
    3   matmul  False   10  2.199343e-06
    4   loop    True    10  2.706000e-07
    5   loop    False   10  7.274286e-07
    6   func    True    10  1.217046e-05
    7   func    False   10  2.495837e-05
    8   rand    True    100 5.199217e-05
    9   rand    False   100 8.149794e-05
    10  matmul  True    100 7.848071e-05
    11  matmul  False   100 2.130794e-05
    12  loop    True    100 2.728571e-07
    13  loop    False   100 3.003743e-06
    14  func    True    100 6.739634e-04
    15  func    False   100 1.146594e-03
    16  rand    True    1000    5.644258e-03
    17  rand    False   1000    8.012790e-03
    18  matmul  True    1000    1.476098e-02
    19  matmul  False   1000    1.613211e-02
    20  loop    True    1000    2.846572e-07
    21  loop    False   1000    3.539849e-05
    22  func    True    1000    1.256926e-01
    23  func    False   1000    1.581177e-01
    24  rand    True    2000    2.061612e-02
    25  rand    False   2000    3.204709e-02
    26  matmul  True    2000    9.866484e-02
    27  matmul  False   2000    1.007234e-01
    28  loop    True    2000    3.011143e-07
    29  loop    False   2000    7.477454e-05
    30  func    True    2000    1.033560e+00
    31  func    False   2000    1.199969e+00
    
    看起来numba在优化循环,所以我不想麻烦把它包括在比较中

    绘图:


    因此,对于5次重复的循环,jit将大大加快速度,直到矩阵乘法变得足够昂贵,使得其他开销相比之下变得微不足道

    我想Numba可以识别
    np.random.rand
    np.dot
    。(如果没有,我认为它不会让你在nopython模式下使用它们。)确实如此。它们由文档中的
    numba
    支持。我主要想知道代码拦截是如何工作的,以及在上面的例子中这是否是收益的来源。你能添加一些设置信息吗?在Win64、Python3.5、numba 0.33上,我肯定只有适度的加速(10-15%)。我使用的是Linux Mint 18、Linux内核4.4.0-45-generic、python 3.5、numba 0.30.1、Intel Xeon CPU E5-1620@3.6Ghz x 4据我所知,答案是1)和2)
    numba
    将函数编译为
    c
    code。因此,它大大加快了循环解析的速度,并加快了具有显著
    python
    开销的
    numpy
    函数的速度(通常通过剥离该开销并强制执行显式数据排序-即大多数构造函数上的no
    axis
    关键字、no
    einsum
    、no
    size
    参数)(
    random.rand
    是一个例外)…所有这些事情都可以在现在更快的
    for
    循环中明确完成)您可能有兴趣修复
    def py_循环():
    代码,因为原来的代码主要是一个
    O(1)
    缩放的(从概念上讲,可能是一个被忽略的快捷方式/屏蔽变量错误,与
    N
    )无关,它扭曲了实验,总是返回
    ~163-294[ns]
    处理持续时间(只确认“常量”)普通调用签名处理开销+JMP/RET持续时间,而不是任何
    N
    -次循环执行代码)。不,您必须返回一个值-一个“there”-生成的值,否则JIT编译器分析看不到通过“silicon silicon”多次循环的任何实际原因-代码,如果它不返回任何内容…最好更仔细地设计
    numba.jit(…)
    测试有效负载,否则您再次求助于不测试O/P认为合理的任何内容。我最初关心的是这一点,但由于没有任何函数具有恒定的时间缩放(没有任何函数返回)我认为numba jit实际上并没有完全优化代码。我可能错了。我不想把计时与返回、类型转换等混为一谈。
    def jit_speedup(d):
        py_time = d[d['jit'] == False]['Time'].mean()
        jit_time = d[d['jit'] == True]['Time'].mean()
        return py_time / jit_time 
    
    import seaborn as sns
    result = df.groupby(['Func', 'N']).apply(jit_speedup).reset_index().rename(columns={0: 'Jit Speedup'})
    result = result[result['Func'] != 'loop']
    sns.factorplot(data=result, x='N', y='Jit Speedup', hue='Func')