我试图找出最有效的方法来创建一个长字节字符串(或bytearray)连接多个较短的字符串时,整个字符串的长度是已知的事先。我制作了这个脚本,并得出了这些结果:
import time
MSG = b'test message'
COUNT = 30000
def bytes_list_test():
tStart = time.clock()
l = []
for i in range(COUNT):
l.append(MSG)
bs = b''.join(l)
print('byte list time:', time.clock() - tStart)
def bytearray_test():
tStart = time.clock()
ba = bytearray()
for i in range(COUNT):
for c in MSG:
ba.append(c)
print('array time:', time.clock() - tStart)
def initialized_bytearray_test():
tStart = time.clock()
ba = bytearray([0x00]*len(MSG)*COUNT)
for i in range(COUNT):
ba[i*len(MSG):i*len(MSG)+len(MSG)] = MSG
print('initialized array time:', time.clock() - tStart)
bytes_list_test()
bytearray_test()
initialized_bytearray_test()
结果:
byte list time: 0.0076534920117410365
array time: 0.08107178658246994
initialized array time: 0.08843219671325642
几个问题:
1)创建一个字节列表并使用join()方法是结果所暗示的方式吗?
2)为什么使用字节列表比使用bytearray要快得多,而bytearray似乎是为这种类型的东西设计的?
3)你可能会认为初始化的数组会比未初始化的数组快,因为初始化的数组不需要调整大小(注意,它偶尔会表现得更好,但不是很多,而且不一致)。它不是因为切片操作而更快吗?
第一个函数创建了一个指向同一对象的指针列表(不是字节列表),然后join
将分配一次内存,COUNT
将调用memcpy
。
您可以通过删除临时列表并使用itertools.repeat
:
def bytes_list_test_opt():
tStart = time.clock()
bs = b''.join(itertools.repeat(MSG, COUNT))
print('byte list opt time:', time.clock() - tStart)
或者,在这个特殊的情况下,简单地使用bytes
对象的*
操作符,它确实做到了:
bs = MSG*COUNT
第二个函数在MSG
上重复迭代,逐字节存储数据,并且必须随着bytearray的增长而重复重新分配内存。
通过将迭代替换为对extend
的单个调用,可以使第二个函数几乎与原始(未优化的)第一个函数一样快:
def bytearray_test_opt():
tStart = time.clock()
ba = bytearray()
for i in range(COUNT):
ba.extend(MSG)
print('array opt time:', time.clock() - tStart)
经过此修改后,第二个函数将比第一个函数慢,只是因为额外的重新分配(在我的测试中约为15%)。
第三个函数使用bytearray
的切片赋值,它接受iterable,并且似乎在做相同的逐字节迭代,而没有意识到它们可以将memcpy
字节放入位置。这看起来像是标准库中的一个可以修复的缺陷。
从前面的优化中可以看到,与逐字节复制相比,分配占用的时间非常少,因此预分配在这里没有明显的影响。你可以通过减少计算来节省一些时间,但这也没有多大帮助:
def initialized_bytearray_test_opt():
tStart = time.clock()
L = len(MSG)
ba = bytearray(L*COUNT)
ofs = 0
for i in range(COUNT):
ba[ofs : ofs+L] = MSG
ofs += L
print('initialized array opt time:', time.clock() - tStart)
我的机器的最终计时:
byte list time: 0.004823000000000001
byte list opt time: 0.0008649999999999977
array time: 0.043324
array opt time: 0.005505999999999997
initialized array time: 0.05936899999999999
initialized array opt time: 0.040164000000000005
注:使用timeit
模块来执行这样的测量,它提供了更高的精度。