万万没想到,除了香农计划,Python3.11竟还有这么多性能提升!( 二 )

Python 3.11:
$ python -m pyperf timeit -s 'd = [0] * 10000' -- 'sum(d)'.....................Mean +- std dev: 39.0 us +- 1.0 usPython3.10 和 3.11 之间的区别在于,通过在 sum 函数的快速加法分支中内联对单个数字 PyLongs 的解包,可以提升在单个数字 PyLongs 上调用 sum 的性能 。这样做可以避免在解包时python/blob/125cdcf504a5d937b575cda3552b233dd44ba127/Python/bltinmodule.c#L2485-L2490" rel="external nofollow noreferrer">调用 python/cpython/blob/de6981680bcf6496e5996a853b2eaa700ed59b2c/Objects/longobject.c#L489" rel="external nofollow noreferrer">PyLong_AsLongAndOverflow 。
值得注意的是,python/cpython/issues/68264#issuecomment-1285351158" rel="external nofollow noreferrer">在某些情况下,Python 3.11 在整数求和时仍然明显慢于 Python 2.7 。我们希望在 Python 中通过python/ideas/discussions/147" rel="external nofollow noreferrer">实现更高效的整数,获得更多的改进 。
精简列表的扩容操作,提升了 list.append 性能在 Python 3.11 中,list.append 有了显著的性能提升(大约快 54%) 。
Python 3.10 的列表 append:
$ python -m pyperf timeit -s \'x = list(map(float, range(10_000)))' -- '[x.append(i) for i in range(10_000)]'.....................Mean +- std dev: 605 us +- 20 usPython 3.11 的列表 append:
$ python -m pyperf timeit -s \'x = list(map(float, range(10_000)))' -- '[x.append(i) for i in range(10_000)]'.....................Mean +- std dev: 392 us +- 14 us对于简单的列表推导式 , 也有一些小的改进:
Python 3.10:
$ python -m pyperf timeit -s \'' -- '[x for x in list(map(float, range(10_000)))]'.....................Mean +- std dev: 553 us +- 19 usPython 3.11:
$ python -m pyperf timeit -s \'' -- '[x for x in list(map(float, range(10_000)))]'.....................Mean +- std dev: 516 us +- 16 us译注:记得在 3.9 版本的时候,Python 优化了调用 list()、dict() 和 range() 等内置类型的速度,在不起眼处 , 竟还能持续优化!
减少了全 unicode 键的字典的内存占用这项优化令 Python 在使用全为 Unicode 键的字典时,缓存的效率更高 。这是因为使用的内存减少了 , 那些 Unicode 键的哈希会被丢弃,因为那些 Unicode 对象已经有哈希了 。
例如,在 64 位平台上,Python 3.10 运行结果:
>>> sys.getsizeof(dict(foo="bar", bar="foo"))232在 Python 3.11 中:
>>> sys.getsizeof(dict(foo="bar", bar="foo"))184(译注:插个题外话,Python 的 getsizeof 是一种“浅计算”,这篇《Python在计算内存时应该注意的问题?》区分了“深浅计算” , 可以让你对 Python 计算内存有更深的理解 。)
提升了使用asyncio.DatagramProtocol 传输大文件的速度asyncio.DatagramProtocol 提供了一个用于实现数据报(UDP)协议的基类 。有了这个优化,使用asyncio UDP 传输大文件(比如 60 MiB)将比 Python 3.10 快 100 多倍 。
这是通过计算一次缓冲区的大小并将其存储在一个属性中来实现的 。这使得通过 UDP 传输大文件时 , asyncio.DatagramProtocol 有着数量级的提速 。
PR msoxzw 的作者提供了以下的 测试脚本 。
对于 math 库:优化了 comb(n, k) 与 perm(n, k=None)Python 3.8 在math 标准库中增加了 comb(n, k) 和 perm(n, k=None) 函数 。两者都用于计算从 n 个无重复的元素中选择 k 个元素的方法数 , comb 返回无序计算的结果,而perm 返回有序计算的结果 。(译注:即一个求组合数,一个求排列数)
3.11 的优化由多个较小的改进组成,比如使用分治算法来实现 Karatsuba 大数乘法,以及尽可能用 C 语言unsigned long long 类型而不是 Python 整数进行comb计算(python/cpython/pull/29090#issue-1031333783" rel="external nofollow noreferrer">*) 。
另外一项改进是针对较小的 k 值(0 <= k <= n <= 67):
(译注:以下两段费解,暂跳过)

对于 0 <= k <= n <= 67, comb(n, k) always fits into a uint64_t. We compute it as comb_odd_part << shift where 2 ** shift is the largest power of two dividing comb(n, k) and comb_odd_part is comb(n, k) >> shift. comb_odd_part can be calculated efficiently via arithmetic modulo 2 ** 64, using three lookups and two uint64_t multiplications, while the necessary shift can be computed via Kummer's theorem: it's the number of carries when adding k to n - k in binary, which in turn is the number of set bits of

推荐阅读