我有lst = [0] * 10**6
过这样的经历:
5.4 ± 0.4 ms bytearray(lst)
5.6 ± 0.4 ms bytes(bytearray(lst))
13.1 ± 0.7 ms bytes(lst)
Python:
3.13.0 (main, Nov 9 2024, 10:04:25) [GCC 14.2.1 20240910]
namespace(name='cpython', cache_tag='cpython-313', version=sys.version_info(major=3, minor=13, micro=0, releaselevel='final', serial=0), hexversion=51183856, _multiarch='x86_64-linux-gnu')
我本来以为bytes(lst)
和bytearray(lst)
会一样快,或者bytearray(lst)
更慢,因为它是更复杂的类型(具有更多功能,因为它是可变的)。但事实恰恰相反。甚至绕行也bytes(bytearray(lst))
比 快得多bytes(lst)
!为什么bytes(lst)
这么慢?
基准测试脚本(在线尝试!):
from timeit import timeit
from statistics import mean, stdev
import random
import sys
setup = 'lst = [0] * 10**6'
codes = [
'bytes(lst)',
'bytearray(lst)',
'bytes(bytearray(lst))'
]
times = {c: [] for c in codes}
def stats(c):
ts = [t * 1e3 for t in sorted(times[c])[:5]]
return f'{mean(ts):5.1f} ± {stdev(ts):3.1f} ms '
for _ in range(25):
random.shuffle(codes)
for c in codes:
t = timeit(c, setup, number=10) / 10
times[c].append(t)
for c in sorted(codes, key=stats):
print(stats(c), c)
print('\nPython:')
print(sys.version)
print(sys.implementation)
受到一个也发现更快的答案的启发。bytearray
正如 @Homer512 在评论中指出的那样,bytearray 和 bytes 在 CPython 中的实现方式截然不同。虽然 GitHub 问题 # 91149为 bytearray 提供了从列表和元组创建的快速路径,但 bytes 并未获得相同的优化。
所述bytearray 的快速路径利用了
PySequence_Fast_ITEMS
,当给定一个列表或一个元组时,它会返回一个对象数组,这样只需遍历该数组即可获得所有项目。另一方面,从列表创建字节必须进行
PyList_GET_ITEM
调用以使用每个索引获取列表中的每个项目,因此性能相对较低。我在 PR # 128214中重构了从列表和元组创建字节的代码,并使用以下基准进行测试:
性能提高了约 81%:
并且确实比在同样的基准上构建字节数组要快: