NumPy表现:uint8 vs.浮动和乘法与除法?

dan*_*451 16 python performance numpy python-2.7

我刚刚注意到,通过仅将乘法变为除法,我的脚本的执行时间几乎减半.

为了研究这个,我写了一个小例子:

import numpy as np                                                                                                                                                                                
import timeit

# uint8 array
arr1 = np.random.randint(0, high=256, size=(100, 100), dtype=np.uint8)

# float32 array
arr2 = np.random.rand(100, 100).astype(np.float32)
arr2 *= 255.0


def arrmult(a):
    """ 
    mult, read-write iterator
    """
    b = a.copy()
    for item in np.nditer(b, op_flags=["readwrite"]):
        item[...] = (item + 5) * 0.5

def arrmult2(a):
    """ 
    mult, index iterator
    """
    b = a.copy()
    for i, j in np.ndindex(b.shape):
        b[i, j] = (b[i, j] + 5) * 0.5

def arrmult3(a):
    """
    mult, vectorized
    """
    b = a.copy()
    b = (b + 5) * 0.5

def arrdiv(a):
    """ 
    div, read-write iterator 
    """
    b = a.copy()
    for item in np.nditer(b, op_flags=["readwrite"]):
        item[...] = (item + 5) / 2

def arrdiv2(a):
    """ 
    div, index iterator
    """
    b = a.copy()
    for i, j in np.ndindex(b.shape):
           b[i, j] = (b[i, j] + 5)  / 2                                                                                 

def arrdiv3(a):                                                                                                     
    """                                                                                                             
    div, vectorized                                                                                                 
    """                                                                                                             
    b = a.copy()                                                                                                    
    b = (b + 5) / 2                                                                                               




def print_time(name, t):                                                                                            
    print("{: <10}: {: >6.4f}s".format(name, t))                                                                    

timeit_iterations = 100                                                                                             

print("uint8 arrays")                                                                                               
print_time("arrmult", timeit.timeit("arrmult(arr1)", "from __main__ import arrmult, arr1", number=timeit_iterations))
print_time("arrmult2", timeit.timeit("arrmult2(arr1)", "from __main__ import arrmult2, arr1", number=timeit_iterations))
print_time("arrmult3", timeit.timeit("arrmult3(arr1)", "from __main__ import arrmult3, arr1", number=timeit_iterations))
print_time("arrdiv", timeit.timeit("arrdiv(arr1)", "from __main__ import arrdiv, arr1", number=timeit_iterations))  
print_time("arrdiv2", timeit.timeit("arrdiv2(arr1)", "from __main__ import arrdiv2, arr1", number=timeit_iterations))
print_time("arrdiv3", timeit.timeit("arrdiv3(arr1)", "from __main__ import arrdiv3, arr1", number=timeit_iterations))

print("\nfloat32 arrays")                                                                                           
print_time("arrmult", timeit.timeit("arrmult(arr2)", "from __main__ import arrmult, arr2", number=timeit_iterations))
print_time("arrmult2", timeit.timeit("arrmult2(arr2)", "from __main__ import arrmult2, arr2", number=timeit_iterations))
print_time("arrmult3", timeit.timeit("arrmult3(arr2)", "from __main__ import arrmult3, arr2", number=timeit_iterations))
print_time("arrdiv", timeit.timeit("arrdiv(arr2)", "from __main__ import arrdiv, arr2", number=timeit_iterations))  
print_time("arrdiv2", timeit.timeit("arrdiv2(arr2)", "from __main__ import arrdiv2, arr2", number=timeit_iterations))
print_time("arrdiv3", timeit.timeit("arrdiv3(arr2)", "from __main__ import arrdiv3, arr2", number=timeit_iterations))
Run Code Online (Sandbox Code Playgroud)

这将打印以下时间:

uint8 arrays
arrmult   : 2.2004s
arrmult2  : 3.0589s
arrmult3  : 0.0014s
arrdiv    : 1.1540s
arrdiv2   : 2.0780s
arrdiv3   : 0.0027s

float32 arrays
arrmult   : 1.2708s
arrmult2  : 2.4120s
arrmult3  : 0.0009s
arrdiv    : 1.5771s
arrdiv2   : 2.3843s
arrdiv3   : 0.0009s
Run Code Online (Sandbox Code Playgroud)

我一直认为乘法在计算上比分裂便宜.然而,对于uint8一个师来说似乎有效率几乎是其两倍.这是否与* 0.5某个事实有关,它必须计算浮点数中的乘法然后将结果转换回整数?

至少对于浮点数乘法似乎比除法更快.这一般是正确的吗?

为什么乘法uint8更广泛而不是float32?我认为8位无符号整数的计算速度要比32位浮点数快得多?!

有人可以"神秘化"这个吗?

编辑:为了获得更多数据,我已经包含了矢量化函数(如建议的)和添加的索引迭代器.矢量化函数要快得多,因此无法真正比​​较.但是,如果timeit_iterations向量化函数的设置值更高,则两者的乘法运算速度更快,uint8并且float32.我想这会让人更加困惑?!

也许乘法实际上总是快于除法,但for循环中的主要性能泄漏不是算术运算,而是循环本身.虽然这并不能解释为什么循环对于不同的操作表现不同.

EDIT2:就像@jotasi已经说过的那样,我们正在寻找divisionmultiplicationint(和uint8)对float(或float32)的完整解释.另外,解释向量化方法和迭代器的不同趋势将是有趣的,因为在向量化的情况下,除法似乎更慢,而在迭代器情况下它更快.

ead*_*ead 8

问题是你的假设,即你测量分裂或乘法所需的时间,这是不正确的.您正在测量除法或乘法所需的开销.

一个人真的要查看确切的代码来解释每个效果,这些效果可能因版本而异.这个答案只能给出一个想法,一个人必须考虑的问题.

问题是int在python 中一个简单的东西根本不简单:它是一个必须在垃圾收集器中注册的真实对象,它的大小随着它的值而增长 - 对于你需要支付的所有东西:例如对于8位整数需要24字节的内存!类似于python-floats.

另一方面,numpy数组由简单的c风格整数/浮点数组成,没有开销,你节省了大量内存,但在访问numpy-array元素时付出了代价.a[i]意味着:必须构造一个python-integer,在垃圾收集器中注册,而且只能使用它 - 这会产生很多开销.

考虑以下代码:

li1=[x%256 for x in xrange(10**4)]
arr1=np.array(li1, np.uint8)

def arrmult(a):    
    for i in xrange(len(a)):
        a[i]*=5;
Run Code Online (Sandbox Code Playgroud)

arrmult(li1)快于25 arrmult(arr1),因为列表中的整数都已经蟒蛇,整数,并且没有被创建!创造物体需要大部分计算时间 - 其他一切都几乎可以忽略不计.


我们来看看你的代码,首先是乘法:

def arrmult2(a):
    ...
    b[i, j] = (b[i, j] + 5) * 0.5
Run Code Online (Sandbox Code Playgroud)

在uint8的情况下,必须发生以下情况(为简单起见,我忽略了+5):

  1. 必须创建一个python-int
  2. 它必须被转换为float(python-float创建),以便能够进行浮点乘法
  3. 并转换回python-int或/和uint8

对于float32,可以做的工作量较少(乘法不会花费太多):1.创建了一个python-float 2.使用后面的float32.

所以float-version应该更快,它就是.


现在让我们来看看这个部门:

def arrdiv2(a):
    ...
    b[i, j] = (b[i, j] + 5)  / 2 
Run Code Online (Sandbox Code Playgroud)

这里的陷阱:所有操作都是整数运算.因此,与乘法相比,不需要转换为python-float,因此我们在乘法的情况下具有更少的开销.对于unint8,除法在你的情况下比乘法"更快".

但是,float32的除法和乘法同样快/慢,因为在这种情况下几乎没有任何改变 - 我们仍然需要创建一个python-float.


现在是矢量化版本:它们使用c风格的"raw"float32s/uint8s而无需转换(及其成本!)到引擎盖下的相应python-objects.为了获得有意义的结果,你应该增加迭代次数(现在运行时间太短,无法确定地说出来).

  1. float32的除法和乘法可以具有相同的运行时间,因为我希望numpy通过乘以将2除以2 0.5(但是要确保必须查看代码).

  2. uint8的乘法应该更慢,因为每个uint8整数必须在乘以0.5之前被转换为浮点数,然后再转换为uint8.

  3. 对于uint8的情况,numpy不能通过乘以0.5来取代除以2,因为它是整数除法.对于许多体系结构,整数除法比浮点乘法慢 - 这是最慢的向量化操作.


PS:我不会过多谈论成本增加与分裂 - 还有太多其他事情会对性能产生更大影响.例如,创建不必要的临时对象,或者如果numpy-array很大并且不适合缓存,那么内存访问将是瓶颈 - 你将看到乘法和除法之间没有区别.


Dun*_*nes 5

此答案仅针对向量化操作,因为ead已回答了其他操作缓慢的原因。

许多“优化”都基于旧硬件。意味着优化适用于旧硬件的假设在新硬件上并不适用。

管道和分区

分工慢。除法运算由多个单元组成,每个单元都必须一个接一个地执行计算。这就是使分裂变慢的原因。

然而,在浮点处理单元 (FPU) 中[在大多数现代 CPU 上很常见],有专门的单元排列在除法指令的“管道”中。一旦一个单元完成,剩下的操作就不需要该单元了。如果您有多个除法运算,则可以在下一个除法运算中启动这些单元而无需执行任何操作。所以虽然每次操作都很慢,但 FPU 实际上可以实现高吞吐量的除法操作。流水线与矢量化不同,但结果大致相同——当您有许多相同的操作要做时,吞吐量会更高。

把管道想象成交通。比较以每小时 30 英里的速度行驶的三个车道与以每小时 90 英里的速度行驶的一条车道。较慢的交通肯定是单独较慢,但三车道的道路仍然具有相同的吞吐量。