Python Numpy 数据类型性能
我做了一些测试,结果有点奇怪。
代码:
import numpy as np
import timeit
setup = """
import numpy as np
A = np.ones((1000,1000,3), dtype=datatype)
"""
datatypes = "np.uint8", "np.uint16", "np.uint32", "np.uint64", "np.float16", "np.float32", "np.float64"
stmt1 = """
A = A * 255
A = A / 255
A = A - 1
A = A + 1
"""
#~ np.uint8 : 1.04969205993
#~ np.uint16 : 1.19391073202
#~ np.uint32 : 1.37279821351
#~ np.uint64 : 2.99286961148
#~ np.float16 : 9.62375889588
#~ np.float32 : 0.884994368045
#~ np.float64 : 0.920502625252
stmt2 = """
A *= 255
A /= 255
A -= 1
A += 1
"""
#~ np.uint8 : 0.959514497259
#~ np.uint16 : 0.988570167659
#~ np.uint32 : 0.963571471946
#~ np.uint64 : 2.07768933333
#~ np.float16 : 9.40085450056
#~ np.float32 : 0.882363984225
#~ np.float64 : 0.910147440048
stmt3 = """
A = A * 255 / 255 - 1 + 1
"""
#~ np.uint8 : 1.05919667881
#~ np.uint16 : 1.20249978404
#~ np.uint32 : 1.58037744789
#~ np.uint64 : 3.47520357571
#~ np.float16 : 10.4792515701
#~ np.float32 : 1.29654744484
#~ np.float64 : 1.80735079168
stmt4 = """
A[:,:,:2] *= A[:,:,:2]
"""
#~ np.uint8 : 1.23270964172
#~ np.uint16 : 1.3260807837
#~ np.uint32 : 1.32571002402
#~ np.uint64 : 1.76836543305
#~ np.float16 : 2.83364821535
#~ np.float32 : 1.31282323872
#~ np.float64 : 1.44151875479
stmt5 = """
A[:,:,:2] = A[:,:,:2] * A[:,:,:2]
"""
#~ np.uint8 : 1.38166223494
#~ np.uint16 : 1.49569114821
#~ np.uint32 : 1.53105315419
#~ np.uint64 : 2.03457943366
#~ np.float16 : 3.01117795524
#~ np.float32 : 1.51807271679
#~ np.float64 : 1.7164808877
stmt6 = """
A *= 4
A /= 4
"""
#~ np.uint8 : 0.698176392658
#~ np.uint16 : 0.709560468038
#~ np.uint32 : 0.701653066443
#~ np.uint64 : 1.64199069295
#~ np.float16 : 4.86752675499
#~ np.float32 : 0.421001675475
#~ np.float64 : 0.433056710408
stmt7 = """
np.left_shift(A, 2, A)
np.right_shift(A, 2, A)
"""
#~ np.uint8 : 0.381521115341
#~ np.uint16 : 0.383545967785
#~ np.uint32 : 0.386147272415
#~ np.uint64 : 0.665969478824
for stmt in [stmt1, stmt2, stmt3, stmt4, stmt5, stmt6, stmt7]:
print stmt
for d in datatypes:
s = setup.replace("datatype", d)
T = timeit.Timer(stmt=stmt, setup=s)
print d,":", min(T.repeat(number=30))
print
print
为什么float16这么慢?而float32却这么快?它的速度常常比整数运算还快。
如果你有任何相关的性能提升建议,我很乐意听听。
这是在Windows 8 64位上运行的Python 2.6.6 32位版本。Numpy 1.6和Numpy 1.7的数字结果类似。现在我会测试一下MKL优化版本:http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy
补充:结果发现MKL版本在某些浮点运算上稍微快一些,但在整数运算上有时候却慢很多:
stmt2 = """
A *= 255
A /= 255
A -= 1
A += 1
"""
#np1.6
#~ np.uint8 : 0.959514497259
#~ np.uint16 : 0.988570167659
#~ np.uint32 : 0.963571471946
#~ np.uint64 : 2.07768933333
#~ np.float16 : 9.40085450056
#~ np.float32 : 0.882363984225
#~ np.float64 : 0.910147440048
# np1.7
#~ np.uint8 : 0.979
#~ np.uint16 : 1.010
#~ np.uint32 : 0.972
#~ np.uint64 : 2.081
#~ np.float16 : 9.362
#~ np.float32 : 0.882
#~ np.float64 : 0.918
# np1.7 mkl
#~ np.uint8 : 1.782
#~ np.uint16 : 1.145
#~ np.uint32 : 1.265
#~ np.uint64 : 2.088
#~ np.float16 : 9.029
#~ np.float32 : 0.800
#~ np.float64 : 0.866
2 个回答
16位浮点数在大多数常见的CPU上并不直接支持(不过显卡厂商似乎在这个数据类型上有参与,所以我预计未来GPU会支持它)。我想它们会以一种比较慢的方式被模拟出来。谷歌告诉我,float16曾经是依赖于硬件的,一些人希望能在不支持它的硬件上进行模拟,虽然我没有找到关于这是否真的发生过的资料。
另一方面,32位浮点数不仅得到原生支持,你还可以利用SIMD指令集扩展对它们进行许多操作的向量化,这样可以大大减少你测试的操作的开销。唯一的例外是数据的重新排列,但在这种情况下,float32和int32的性能相当,二者都可以使用相同的SIMD指令来加载和存储更大的内存块。
虽然整数运算也有SIMD指令,但它们不太常见(例如,SEE在比浮点版本更晚的版本中引入了这些指令),而且通常也不那么复杂。我猜测(你的NumPy版本)可能没有对你来说较慢的操作的SIMD实现。或者,整数运算可能没有那么优化:浮点数在许多容易进行向量化的应用中被广泛使用,这些应用的性能非常重要(例如图像/媒体/视频的编码和解码),所以它们可能会得到更多的优化。
半精度运算(float16)在numpy中可能需要“模拟”,因为在底层的C语言和相关的处理器指令中没有对应的类型。另一方面,单精度(float32)和双精度(float64)运算可以使用本地数据类型高效地进行。
至于单精度运算的良好性能:现代处理器有高效的单元来处理向量化的浮点运算(比如AVX),这也是为了保证良好的多媒体性能。