众所周知,Python3.11版本带来了很大的性能提升,那么到底优化了哪些方面呢?除了著名的“香农计划”之外,它还包含哪些与性能相关的优化?本文带你一探究竟!作者:BeshrKayali译者:猫下豌豆花@Python猫英文:https://log.beshr.com/python-311-speedup-part-1转载请保留作者及译者信息!Python3.11于日前发布,一如既往地带来了许多新特性,例如异常组、细粒度的错误定位和堆栈回溯、标准库对TOML解析的支持,当然还有万众期待的更快速度CPython项目带来的改进。根据pyperformance基准测试,CPython3.11平均比CPython3.10快25%。这种改进的原因之一是Guido将其命名为“ProjectShannon”(即更快的CPython)。对于3.11版本,计划在两个主要方向做大量优化:启动和运行时。此外,Python3.11包含不属于Shannon计划的其他优化。在这篇文章中,我将详细介绍3.11.0稳定版中一般优化的细节(即非更快的CPython项目的改进)。(译注:作者说他会再写一篇文章介绍fasterCPython的改进细节,到时候我会继续翻译,敬请期待!)Python中的整数改进了数字PyLongs求和和压缩列表的扩展操作,提高了list.append的性能减少了具有完整unicode键的字典的内存使用提高了使用asyncio.DatagramProtocol传输大文件的速度对于数学库:optimizedcomb(n,k)对于带有perm(n,k=None)的统计库:优化mean(data)、variance(data,xbar=None)和stdev(data,xbar=None)的unicodedata.normalize())纯ASCII字符串,并升级到恒定时间优化一些printf样式的%格式化代码使用格式化字符串文字是格式化字符串的最快方法。Python3.10中的一个简单基准测试:$python-mpyperftimeit-s\'k="foo";v="bar"'--'"%s=%r"%(k,v)'...........平均+-标准偏差:187ns+-8ns但使用f-string似乎快了42%:$python-mpyperftimeit-s\'k="foo";v="bar"'--'f"{k!s}={v!r}"'..........均值+-stddev:131ns+-9ns优化性能的方法是将简单的C样式格式化方法转换为f字符串方法。在3.11.0中,只有%s,%r,%a转换了,但是目前有一个pullrequest要合并,会支持:%d,%i,%u,%o,%x,%X,%f,%e,%g,%F,%E,%G。例如,这里是Python3.11中相同基准测试的结果:$python-mpyperftimeit-s\'k="foo";v="bar"'--'"%s=%r"%(k,v)'......平均值+-标准偏差:100ns+-5ns大约87%快点!当然,3.11中的其他优化也对此做出了贡献,例如更快的解释器启动时间。优化Python中大整数除法在Python3.10中:python-mpyperftimeit-s'x=10**1000'--'x//10'..................平均值+-标准偏差:1.18us+-0.02us在Python3.11中:python-mpyperftimeit-s'x=10**1000'--'x//10'...............平均+-标准偏差:995ns+-15ns大约快18%。这种优化源于MarkDickinson的一项发现,即编译器总是会生成128:64除法指令,尽管处理的是30位值。即使在x64上,Python的划分也有些残缺。假设30位数字,多精度除法所需的基本结构是64位除以32位无符号整数除法,产生32位商(理想情况下是32位余数)。有一条x86/x64指令可以执行此操作,即DIVL。但是当前版本的GCC和Clang显然不能在不使用内联汇编的情况下从longobject.c发出该指令——它们将只使用DIVQ(x64上的128位x64位除法,尽管前面的64位设置为零),并在x86上使用固有的\_\_udivti3或\_\_udivti4。–MarkDickinson(全文)在PyLongs中优化数字求和这里有一个问题,发现求和在Python2.7中比在Python3中快得多。不幸的是,在某些情况下,3.11.0似乎仍然如此。Python2.7:$python-mpyperftimeit-s'd=[0]*10000'--'sum(d)'.............平均值+-标准偏差:37.4us+-1.1usPython3.10:$python-mpyperftimeit-s'd=[0]*10000'--'sum(d)'.....................均值+-stddev:52.7us+-1.3usPython3.11:$python-mpyperftimeit-s'd=[0]*10000'--'sum(d)'...........平均+-stddev:39.0us+-1.0usPython3.10和3.11的区别在于,通过使用sum函数的快速加法分支,内联单个数字PyLong的解包提高了对单个数字PyLong调用sum的性能。这样做可以避免在解包时调用PyLong_AsLongAndOverflow。值得注意的是,在某些情况下对整数求和时,Python3.11仍然明显慢于Python2.7。我们希望通过实现更高效的整数来看到Python的更多改进。紧凑列表的扩展操作提高了list.append的性能在Python3.11中,list.append有显着的性能提升(大约快54%)。Python3.10的列表追加:$python-mpyperftimeit-s\'x=list(map(float,range(10_000)))'--'[x.append(i)foriinrange(10_000)]'..........平均值+-标准偏差:605us+-20usPython3.11的列表附加:$python-mpyperftimeit-s\'x=list(map(float,range(10_000)))'--'[x.append(i)foriinrange(10_000)]'.........均值+-标准偏差:392us+-14us对于简单的列表理解,有一些小改进:Python3.10:$python-mpyperftimeit-s\''--'[xforxinlist(map(float,range(10_000)))]'.............平均值+-标准偏差:553us+-19usPython3.11:$python-mpyperftimeit-s\''--'[xforxinlist(map(float,range(10_000)))]'.........均值+-stddev:516us+-16us可以继续优化!减少具有所有unicode键的字典的内存占用此优化使Python缓存在使用具有所有unicode键的字典时更加高效。这是因为使用的内存较少,并且这些Unicode键的哈希值被丢弃,因为那些Unicode对象已经有哈希值。例如,在64位平台上,Python3.10结果为:>>>sys.getsizeof(dict(foo="bar",bar="foo"))232在Python3.11中:>>>sys.getsizeof(dict(foo="bar",bar="foo"))184Computationalmemory理解更深。)使用asyncio.DatagramProtocol提高了传输大文件的速度asyncio.DatagramProtocol提供了一个实现Datagram(UDP)协议的基类。通过此优化,使用asyncioUDP传输大文件(例如60MiB)的速度将比使用Python3.10快100倍以上。这是通过一次计算缓冲区的大小并将其存储在属性中来实现的。这使得asyncio.DatagramProtocol在通过UDP传输大文件时速度提高了几个数量级。PRmsoxzw的作者提供了以下测试脚本。对于数学库:优化的comb(n,k)和perm(n,k=None)Python3.8在数学标准库中添加了comb(n,k)和perm(n,k=None)函数。两者都用于统计从n个唯一元素中选出k个元素的方法数,comb返回无序计算的结果,perm返回有序计算的结果。(译注:一种用于组合,一种用于排列)3.11的优化包括几个较小的改进,例如使用分治算法实现Karatsuba的大数乘法,尽可能使用C语言unsignedlonglong类型而不是用于梳状计算的Python整数(*)。另外一项修改是针比较小的k值(0<=k<=n<=67):(译注:以下两段费解,暂跳过)对于0<=k<=n<=67,comb(n,k)总是适合uint64_t。我们将其计算为comb_odd_part<
