萬萬沒想到,除了香農計劃,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

推薦閱讀