分配给数组时Numba慢吗?

     2023-02-16     91

关键词:

【中文标题】分配给数组时Numba慢吗?【英文标题】:Numba slow when assigning to an array? 【发布时间】:2013-08-06 22:26:01 【问题描述】:

Numba 似乎是加速数字代码执行的绝佳解决方案。但是,当对数组进行赋值时,Numba 似乎比标准 Python 代码慢。考虑这个例子,比较四个替代方案,有/没有 Numba,写入一个数组/标量:

(故意将计算保持非常简单,以专注于问题,即分配给标量与分配给数组单元格)

@autojit
def fast_sum_arr(arr):
    z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

def sum_arr(arr):
    z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

@autojit
def fast_sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

def sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

使用 IPython 的 %timeit 评估我得到的四个备选方案:

In [125]: %timeit fast_sum_arr(arr)
100 loops, best of 3: 10.8 ms per loop

In [126]: %timeit sum_arr(arr)
100 loops, best of 3: 4.11 ms per loop

In [127]: %timeit fast_sum_sclr(arr)
100000 loops, best of 3: 10 us per loop

In [128]: %timeit sum_sclr(arr)
100 loops, best of 3: 2.93 ms per loop

sum_arr,不是用 Numba 编译的,比用 Numba 编译的 fast_sum_arr 快两倍多。另一方面,用 Numba 编译的 fast_sum_sclr 比没有用 Numba 编译的 sum_sclr 快两个数量级以上。

所以 Numba 在加速 sum_sclr 的任务上表现得非常好,但实际上使 sum_arr 执行得更慢。 sum_sclr 和 sum_arr 之间的唯一区别是前者分配给标量,而后者分配给数组单元。

不知道有没有关系,不过最近在博客http://www.phi-node.com/看到了以下内容:

“事实证明,当 Numba 遇到它不直接支持的任何构造时,它会切换到(非常)慢的代码路径。”

博客作者使用 if 语句而不是 Python 的 max() 让 Numba 执行得更快。

对此有何见解?

谢谢,

FS

【问题讨论】:

我不明白你的循环应该做什么。这不是有效的z[1:] += arr[1:],还是因为zr 具有相同的值,z[1:] *= 2?我希望这比任何显式循环都快得多,但我不一定希望编译器能够分辨出来。 【参考方案1】:

这里慢的是 arr.copy() 函数,而不是对数组的写访问。证明:

# -*- coding: utf-8 -*-
from numba import autojit
from Timer import Timer
import numpy as np

@autojit
def fast_sum_arr(arr, z):
    #z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

def sum_arr(arr, z):
    #z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

@autojit
def fast_sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

def sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

if __name__ == '__main__':
    vec1 = np.ones(1000)
    z = vec1.copy()
    with Timer() as t0:
        for i in range(10000):
            pass
    print "time for empty loop ", t0.secs
    print
    with Timer() as t1:
        for i in range(10000):
            sum_arr(vec1, z)
    print "time for sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6
    with Timer() as t1:
        for i in range(10000):
            fast_sum_arr(vec1, z)
    print "time for fast_sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6
    with Timer() as t1:
        for i in range(10000):
            sum_sclr(vec1)
    print "time for sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6
    with Timer() as t1:
        for i in range(10000):
            fast_sum_sclr(vec1)
    print "time for fast_sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6

"""
time for empty loop  0.000312089920044

time for sum_arr       [µs]:    432.02688694
time for fast_sum_arr  [µs]:      7.43598937988
time for sum_arr       [µs]:    284.574580193
time for fast_sum_arr  [µs]:      5.74610233307
"""

【讨论】:

视错觉。这更快的事实取决于 numba 使用延迟初始化的事实。第一次在同一会话中调用该函数时速度很慢,然后从第二次到第 10000 次时速度更快。因此第一个较慢,平均消失。尝试使用 1 而不是 10000 来调用它,并尝试在函数内部使用 copy 来调用它。你会看到 copy() 的位置不是瓶颈。【参考方案2】:

我对 numba 知之甚少,但如果我们对它在幕后所做的事情做出一些基本假设,我们可以推断为什么 autojit 版本较慢以及如何通过微小的更改来加快它的速度...

让我们从 sum_arr 开始,

1 def sum_arr(arr):
2     z = arr.copy()
3     M = len(arr)
4     for i in range(M):
5         z[i] += arr[i]
6 
7     return z

很清楚这里发生了什么,但让我们选择可以重写为的第 5 行

1 a = arr[i]
2 b = z[i]
3 c = a + b
4 z[i] = c

Python 将进一步改变这一点

1 a = arr.__getitem__(i)
2 b = arr.__getitem__(i) 
3 c = a.__add__(b)
4 z.__setitem__(i, c)

a,b 和 c 都是 numpy.int64(或类似)的实例

我怀疑 numba 正在尝试检查这些项目的日期类型并将它们转换为一些 numba 本机数据类型(我看到的 numpy 代码最大的减速之一是无意中从 python 数据类型切换到 numpy 数据类型)。如果这确实发生了,numba 至少进行 3 次转换,2 次 numpy.int64 -> 本机,1 次本机 -> numpy.int64,或者中间体可能更糟(numpy.int64 -> python int -> 本机(c诠释))。我怀疑 numba 会在检查数据类型时增加额外的开销,可能根本不会优化循环。让我们看看如果我们从循环中移除类型更改会发生什么......

1 @autojit
2 def fast_sum_arr2(arr):
3     z = arr.tolist()
4     M = len(arr)
5     for i in range(M):
6         z[i] += arr[i]
7 
8     return numpy.array(z)

第 3 行的细微变化是 tolist 而不是 copy,将数据类型更改为 Python ints,但我们仍然在第 6 行有一个 numpy.int64 -> native。让我们将其重写为 z[i] += z[我]

1 @autojit
2 def fast_sum_arr3(arr):
3     z = arr.tolist()
4     M = len(arr)
5     for i in range(M):
6         z[i] += z[i]
7 
8     return numpy.array(z)

通过所有更改,我们看到了相当可观的加速(尽管它不一定能击败纯 python)。当然,arr+arr,就是傻得快。

  1 import numpy
  2 from numba import autojit
  3 
  4 def sum_arr(arr):
  5     z = arr.copy()
  6     M = len(arr)
  7     for i in range(M):
  8         z[i] += arr[i]
  9 
 10     return z
 11 
 12 @autojit
 13 def fast_sum_arr(arr):
 14     z = arr.copy()
 15     M = len(arr)
 16     for i in range(M):
 17         z[i] += arr[i]
 18     
 19     return z
 20 
 21 def sum_arr2(arr):
 22     z = arr.tolist()
 23     M = len(arr)
 24     for i in range(M):
 25         z[i] += arr[i]
 26 
 27     return numpy.array(z)
 28 
 29 @autojit
 30 def fast_sum_arr2(arr):
 31     z = arr.tolist()
 32     M = len(arr)
 33     for i in range(M):
 34         z[i] += arr[i]
 35         
 36     return numpy.array(z)
 37     
 38 def sum_arr3(arr):
 39     z = arr.tolist()
 40     M = len(arr)
 41     for i in range(M):
 42         z[i] += z[i]
 43         
 44     return numpy.array(z)
 45 
 46 @autojit
 47 def fast_sum_arr3(arr):
 48     z = arr.tolist()
 49     M = len(arr)
 50     for i in range(M):
 51         z[i] += z[i]
 52 
 53     return numpy.array(z)
 54 
 55 def sum_arr4(arr):
 56     return arr+arr
 57 
 58 @autojit
 59 def fast_sum_arr4(arr):
 60     return arr+arr
 61 
 62 arr = numpy.arange(1000)

还有时间,

In [1]: %timeit sum_arr(arr)
10000 loops, best of 3: 129 us per loop

In [2]: %timeit sum_arr2(arr)
1000 loops, best of 3: 232 us per loop

In [3]: %timeit sum_arr3(arr)
10000 loops, best of 3: 51.8 us per loop

In [4]: %timeit sum_arr4(arr)
100000 loops, best of 3: 3.68 us per loop

In [5]: %timeit fast_sum_arr(arr)
1000 loops, best of 3: 216 us per loop

In [6]: %timeit fast_sum_arr2(arr)
10000 loops, best of 3: 65.6 us per loop

In [7]: %timeit fast_sum_arr3(arr)
10000 loops, best of 3: 56.5 us per loop

In [8]: %timeit fast_sum_arr4(arr)
100000 loops, best of 3: 2.03 us per loop

【讨论】:

有趣的见解。在我的测试中,我得到了 有趣的见解。在我的测试中,未编译的函数仍然优于已编译的函数。差异不是很显着。标量情况 (fast_sum_sclr) 和向量情况 (fast_sum_arrX) 中的加速度之间的显着差异仍未得到解释。【参考方案3】:

是的,Numba 使用延迟初始化,所以第二次调用它会更快。 对于大型数组,尽管延迟初始化,numba 仍然比 no-numba 好。

尝试以下取消注释不同的 b

import time
import numpy as np

from numba import jit, autojit


@autojit
def fast_sum_arr(arr):
    z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

def sum_arr(arr):
    z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

@autojit
def fast_sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

def sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

b = np.arange(100)
# b = np.arange(1000000)
# b = np.arange(100000000)

print('Vector of len \n'.format(len(b)))

print('Sum ARR:\n')

time1 = time.time()
sum_arr(b)
time2 = time.time()
print('No numba:          '.format(time2 - time1))

time1 = time.time()
fast_sum_arr(b)
time2 = time.time()
print('Numba first time:  '.format(time2 - time1))

time1 = time.time()
fast_sum_arr(b)
time2 = time.time()
print('Numba second time: '.format(time2 - time1))

print('\nSum SCLR:\n')

time1 = time.time()
sum_sclr(b)
time2 = time.time()
print('No numba:          '.format(time2 - time1))

time1 = time.time()
fast_sum_sclr(b)
time2 = time.time()
print('Numba first time:  '.format(time2 - time1))

time1 = time.time()
fast_sum_sclr(b)
time2 = time.time()
print('Numba second time: '.format(time2 - time1))

在我使用 python 3 的系统上,numba 0.34.0 得到

"""
Vector of len 100

Sum ARR:

No numba:          7.414817810058594e-05
Numba first time:  0.07130813598632812
Numba second time: 3.814697265625e-06

Sum SCLR:

No numba:          2.6941299438476562e-05
Numba first time:  0.05761408805847168
Numba second time: 1.4066696166992188e-05
"""

"""
Vector of len 1000000

Sum ARR:

No numba:          0.3144559860229492
Numba first time:  0.07181787490844727
Numba second time: 0.0014197826385498047

Sum SCLR:

No numba:          0.15929198265075684
Numba first time:  0.05956888198852539
Numba second time: 0.00037789344787597656
"""

"""
Vector of len 100000000

Sum ARR:

No numba:          30.345629930496216
Numba first time:  0.7232880592346191
Numba second time: 0.586756706237793

Sum SCLR:

No numba:          16.271318912506104
Numba first time:  0.11036324501037598
Numba second time: 0.06010794639587402
"""

有趣的是,第一次调用和第二次调用之间的计算时间差异随着数组大小的增加而减小。不过,我不知道它为什么会这样工作。

【讨论】:

尝试使用 numba 循环 numpy 数组时出错

】尝试使用numba循环numpy数组时出错【英文标题】:Gettingerrorwhentryingtoloopnumpyarrayswithnumba【发布时间】:2022-01-0121:21:01【问题描述】:你好!我正在尝试使用numba来加快numpy数组的循环。我的代码:@nb.njitdef_complicated(heat_list_,raw_arra... 查看详情

调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?

】调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?【英文标题】:Whencallingamethodandassigningreturnedvaluetoanarray,whyisC#usingthearrayreferencewhenmethodwascalled?调用方法并将返回值分配给数组时,为什么C#在调用方法时... 查看详情

将值分配给二维数组时的垃圾值

】将值分配给二维数组时的垃圾值【英文标题】:Garbagevalueswhenassigningvaluestotwodimensionalarray【发布时间】:2012-10-1820:25:47【问题描述】:我对汇编完全陌生,在为我的二维“数组”赋值时遇到了一个问题。我的数组是一个名为scre... 查看详情

分配给它时动态增长一个python数组

】分配给它时动态增长一个python数组【英文标题】:Dynamicallygrowingapythonarraywhenassigningtoit【发布时间】:2014-01-0106:41:19【问题描述】:我想在python中填充一个数组,以便我可以使用id编号来索引数组。并非所有索引都存在于数组... 查看详情

“错误:分配给带有数组类型错误的表达式”当我分配结构字段时(C)

】“错误:分配给带有数组类型错误的表达式”当我分配结构字段时(C)【英文标题】:"error:assignmenttoexpressionwitharraytypeerror"whenIassignastructfield(C)【发布时间】:2016-09-1013:12:32【问题描述】:我是一名初学者C程序员,昨... 查看详情

分配给结构中的静态数组时堆缓冲区溢出

】分配给结构中的静态数组时堆缓冲区溢出【英文标题】:heap-buffer-overflowwhenassigningtostaticarrayinstruct【发布时间】:2019-07-2609:45:38【问题描述】:我有一个结构体structpixel_graph_headerintpixels[ROWS][COLS];;typedefstructpixel_graph_header*graph;RO... 查看详情

将字符分配给在 C 中使用 malloc 声明的二维数组时收到警告

】将字符分配给在C中使用malloc声明的二维数组时收到警告【英文标题】:Gettingwarningswhenassigningcharacterstoa2darraydeclaredusingmallocinC【发布时间】:2016-02-0913:47:02【问题描述】:我正在学习如何使用malloc创建动态数组。我正在尝试为... 查看详情

强化学习技巧五:numba提速python程序(代码片段)

...代码的JIT编译器,经过numba编译的python代码(仅限数组运算),其运行速度可以接近C或FORTRAN语言。numba使用情况使用numpy数组做大量科学计算时使用for循环时1.numba使用导入numpy、numba及其编译器importnumpyasnpimportnumbaf... 查看详情

如何确保将字符串数组作为字段的结构在分配给另一个变量时遵循值语义

】如何确保将字符串数组作为字段的结构在分配给另一个变量时遵循值语义【英文标题】:Howtoensureastructwithastringarrayasafieldwouldfollowvaluesemanticswhileassigningtoanothervariable【发布时间】:2013-05-0320:41:48【问题描述】:在MSDNdocumentation... 查看详情

如何在 STL 中使用指向向量的指针,就像我们在将数组地址传递给另一个函数时将指针分配给数组一样?

...指针,就像我们在将数组地址传递给另一个函数时将指针分配给数组一样?【英文标题】:HowtousepointerstothevectorinSTLlikeweassignpointerstoaarraywhilepassingaddressofarraytoanotherfunction?【发布时间】:2021-12-1416:59:09【问题描述】:如果是数组-... 查看详情

分配给整数时的字符串数组会产生一个奇怪的值[重复]

】分配给整数时的字符串数组会产生一个奇怪的值[重复]【英文标题】:Stringarraywhenassignedtoanintegerproducesaweirdvalue[duplicate]【发布时间】:2020-09-2423:19:04【问题描述】:我正试图找出一个朋友发给我的代码,但我不知道为什么存在... 查看详情

java.lang.ArrayStoreException:将不正确类型的值分配给 Object [] 数组时 [重复]

】java.lang.ArrayStoreException:将不正确类型的值分配给Object[]数组时[重复]【英文标题】:java.lang.ArrayStoreException:whenassigningvaluewithincorrecttypetoObject[]array[duplicate]【发布时间】:2018-11-1923:31:35【问题描述】:我来自C背景。我不能在foo(... 查看详情

对删除分配给结构数组的动态内存感到困惑

】对删除分配给结构数组的动态内存感到困惑【英文标题】:Confusedaboutdeletingdynamicmemoryallocatedtoarrayofstruct【发布时间】:2013-07-1009:15:00【问题描述】:我遇到了内存泄漏问题,它与类中的结构数组有关(不确定它们在类中是否... 查看详情

在 C++ 中,当一个数组的元素在循环中多次使用时,将其分配给另一个元素会更好吗?

】在C++中,当一个数组的元素在循环中多次使用时,将其分配给另一个元素会更好吗?【英文标题】:InC++,whenanelementofanarrayisusedmultipletimesinaloop,isitbettertoassignittoanotherelement?【发布时间】:2019-07-3007:12:49【问题描述】:假设我有... 查看详情

定义数组而不分配它

】定义数组而不分配它【英文标题】:defineArraywithoutallocatingit【发布时间】:2021-11-1408:23:59【问题描述】:我看到Numba不支持字典列表...因此,我决定改用2DNumpy数组。这很可悲:(我遇到的第二个问题是我想按需创建这个数组。这... 查看详情

从二维数组中的“char *”类型错误分配给“char [100] [100]”类型时的类型不兼容

】从二维数组中的“char*”类型错误分配给“char[100][100]”类型时的类型不兼容【英文标题】:Incompatibletypeswhenassigningtotype\'char[100][100]\'fromtype\'char*\'errorin2darray【发布时间】:2021-05-1019:49:57【问题描述】:我发现有char[100];的类似... 查看详情

如何将“for循环”中的值分配给数组[重复]

】如何将“for循环”中的值分配给数组[重复]【英文标题】:Howtoassignavaluefroma"forloop"toanarray[duplicate]【发布时间】:2021-05-1422:37:56【问题描述】:我试图弄清楚在发生for循环时如何将值分配给数组。我试过显示数组中的一... 查看详情

Numba:@jit 中的并行标志在我的带有 numpy 2D 数组的代码中不起作用

】Numba:@jit中的并行标志在我的带有numpy2D数组的代码中不起作用【英文标题】:Numba:parallelflagin@jitdoesnotworkinmycodewithnumpy2Darrays【发布时间】:2022-01-1703:48:59【问题描述】:首先,我问了这个问题Numba:Whyguvectorizeissoslow?。当我发现... 查看详情