我刚刚注意到,我的一个脚本的执行时间几乎一半,只需将乘法改为除法。
为了研究这个问题,我写了一个小例子:
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))
这将打印以下时间:
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
我一直认为乘法计算起来比除法便宜。然而,对于
uint8
这是否与这样一个事实有关,即* 0.5
必须计算浮点中的乘法,然后将结果强制转换回整数?至少对于浮点数来说,乘法似乎比除法快。这通常是真的吗?
为什么
uint8
中的乘法比float32
中的乘法更容易扩展?我认为8位无符号整数应该比32位浮点更快计算?!有人能“揭开”这个秘密吗?
编辑:为了获得更多的数据,我还包括了向量化函数(如建议的)和添加的索引迭代器。矢量化函数更快,因此不具有可比性。然而,如果矢量化函数的
timeit_iterations
设置得更高,则结果表明,uint8
和float32
的乘法速度更快。我想这更让人困惑?!也许乘法实际上总是比除法快,但是for循环中的主要性能泄漏不是算术运算,而是循环本身。尽管这不能解释为什么循环在不同的操作中表现不同。
edit2:like@jotasi already stated,we are looking for a full explanation of
division
vs.multiplication
andint
(oruint8
)vs.float
(orfloat32
)。此外,解释矢量化方法和迭代器的不同趋势也很有趣,就像在矢量化情况下一样,除法似乎较慢,而在迭代器情况下则更快。 最佳答案
问题是你的假设,你测量除法或乘法所需的时间,这是不正确的。您正在测量除法或乘法所需的开销。
我们必须查看准确的代码来解释每种效果,因为不同版本的效果不同。这个答案只能给出一个想法,一个必须考虑的问题。
问题是,在python中,simpleint
一点也不简单:它是一个必须在垃圾收集器中注册的真实对象,它的大小随它的值而增长-对于您必须支付的所有费用:例如,需要一个8位整数24字节的内存!类似的情况也适用于python float。
另一方面,numpy数组由简单的C样式整数/浮点数组成,没有开销,可以节省大量内存,但在访问numpy数组的元素时会为此付出代价。a[i]
意味着:必须构造一个python整数,并在垃圾收集器中注册,而且只能使用它-这会产生很多开销。
考虑此代码:
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;
arrmult(li1)
比arrmult(arr1)
快25,因为列表中的整数已经是python in t,不必创建!计算时间中最大的一部分是创建对象所需要的——其他的一切几乎都可以忽略不计。让我们看一下您的代码,首先是乘法:
def arrmult2(a):
...
b[i, j] = (b[i, j] + 5) * 0.5
对于uint8,必须发生以下情况(为了简单起见,我忽略了+5):
必须创建python int
它必须强制转换为float(创建python float),以便能够进行float乘法。
并返回到python int或/和uint8
对于float32,要做的工作较少(乘法不需要花费太多):
1。创建了一个python float
2。后抛浮筒32。
所以浮动版本应该更快。
现在让我们来看看这个部门:
def arrdiv2(a):
...
b[i, j] = (b[i, j] + 5) / 2
这里的陷阱是:所有的操作都是整数操作。因此,与乘法相比,不需要强制转换为python float,因此与乘法相比,我们的开销更少。对于unint8,除法比乘法“快”。
然而,对于float32来说,除法和乘法的速度是一样的快/慢,因为在这种情况下几乎没有什么变化——我们仍然需要创建一个python float。
现在,矢量化版本:它们使用C样式的“原始”float32s/uint8s,不需要转换(及其成本!)到引擎盖下相应的python对象。为了得到有意义的结果,您应该增加迭代的次数(现在运行时间太小,无法确定地说些什么)。
float32的除法和乘法可以有相同的运行时间,因为我希望numpy用2替换除法,然后乘以
0.5
(但要确保有人必须查看代码)。uint8的乘法应该较慢,因为每个uint8整数必须在乘0.5之前强制转换为浮点,而不是在乘0.5之后强制转换回uint8。
对于uint8情况,numpy不能用0.5替换乘2的除法,因为它是整数除法。对于许多体系结构来说,整数除法比浮点乘法慢——这是最慢的矢量化操作。
附言:我不会对成本的乘法与除法有太多的顾虑——还有太多其他的东西会对性能产生更大的影响。例如,创建不必要的临时对象,或者如果numpy数组很大并且不适合缓存,那么内存访问将成为瓶颈——您将看到乘法和除法之间没有任何区别。
关于python - NumPy表现:uint8 vs. float 和乘法与除法?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39104562/