[英]NumPy performance: uint8 vs. float and multiplication vs. division?
我刚刚注意到,通过仅将乘法变为除法,我的脚本的执行时间几乎减半。
为了研究这个,我写了一个小例子:
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 :就像@jotasi已经说过的那样,我们正在寻找division
与multiplication
和int
(或uint8
)与float
(或float32
)的完整解释。 另外,解释向量化方法和迭代器的不同趋势将是有趣的,因为在向量化的情况下,除法似乎更慢,而在迭代器情况下它更快。
问题是你的假设,即你测量分裂或乘法所需的时间,这是不正确的。 您正在测量除法或乘法所需的开销。
人们真的要查看确切的代码来解释每种效果,这些效果因版本而异。 这个答案只能给出一个想法,一个人必须考虑的问题。
问题是在python中一个简单的int
根本不简单:它是一个必须在垃圾收集器中注册的真实对象,它的大小随着它的值而增长 - 对于你需要支付的所有内容:例如8bit需要整数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;
arrmult(li1)
比arrmult(arr1)
快25,因为列表中的整数已经是python-ints而不必创建! 创造物体需要大部分计算时间 - 其他一切都几乎可以忽略不计。
我们来看看你的代码,首先是乘法:
def arrmult2(a):
...
b[i, j] = (b[i, j] + 5) * 0.5
在uint8的情况下,必须发生以下情况(为简单起见,我忽略了+5):
对于float32,可以做的工作量较少(乘法不会花费太多):1。创建了一个python-float 2.使用后面的float32。
所以float-version应该更快,它就是。
现在让我们来看看这个部门:
def arrdiv2(a):
...
b[i, j] = (b[i, j] + 5) / 2
这里的陷阱:所有操作都是整数运算。 因此,与乘法相比,不需要转换为python-float,因此我们在乘法的情况下具有更少的开销。 对于unint8,除法在你的情况下比乘法“更快”。
但是,float32的除法和乘法同样快/慢,因为在这种情况下几乎没有任何改变 - 我们仍然需要创建一个python-float。
现在是矢量化版本:它们使用c风格的“raw”float32s / uint8s而无需转换(及其成本!)到引擎盖下的相应python-objects。 为了获得有意义的结果,你应该增加迭代次数(现在运行时间太短,无法确定地说出来)。
float32的除法和乘法可以具有相同的运行时间,因为我希望numpy通过乘以0.5
来将除法替换为2(但是要确保必须查看代码)。
uint8的乘法应该更慢,因为每个uint8整数必须在乘以0.5之前被转换为浮点数,然后再转换为uint8。
对于uint8的情况,numpy不能通过乘以0.5来取代除以2,因为它是整数除法。 对于许多体系结构,整数除法比浮点乘法慢 - 这是最慢的向量化操作。
PS:我不会过多谈论成本增加与分裂 - 还有太多其他事情会对性能产生更大影响。 例如,创建不必要的临时对象,或者如果numpy-array很大并且不适合缓存,那么内存访问将是瓶颈 - 你将看到乘法和除法之间没有区别。
这个答案只关注矢量化操作,因为其他操作缓慢的原因已由ead回答。
许多“优化”都基于旧硬件。 这些假设意味着在旧硬件上实现优化并不适用于较新的硬件。
分工很慢。 分部操作由几个单元组成,每个单元必须一个接一个地执行一个计算。 这就是分裂缓慢的原因。
然而,在浮点处理单元(FPU)[在大多数现代CPU上通用],存在布置在用于划分指令的“流水线”中的专用单元。 一旦完成一个单元,其余操作就不需要该单元。 如果你有几个除法运算,你就可以在下一个除法运算中得到这些单位。 因此,虽然每个操作都很慢,但FPU实际上可以实现高吞吐量的除法运算。 管道传输与矢量化不同,但结果大致相同 - 当您有许多相同的操作时,吞吐量会更高。
想想像流量这样的管道。 比较以30英里/小时的速度行驶的三条车道与一条以90英里/小时的速度行驶的车道。 较慢的流量肯定会单独放慢,但三车道仍然具有相同的吞吐量。
这是因为你将一个int乘以一个浮点数并将结果存储为一个int。 尝试使用不同的整数或浮点值进行arr_mult和arr_div测试以进行乘法/除法。 特别是,比较乘以'2'并乘以'2'。
这是第一次操作,通常需要更长时间才能“预热”(例如,分配内存,缓存)。
使用相反的分割和相乘顺序查看相同的效果:
>>> print_time("arrdiv", timeit.timeit("arrdiv(arr2)", "from __main__ import arrdiv, arr2", number=timeit_iterations))
>>> print_time("arrmult", timeit.timeit("arrmult(arr2)", "from __main__ import arrmult, arr2", number=timeit_iterations))
arrdiv: 3.2630s
arrmult: 2.5873s
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.