当前位置: 首页 > 后端技术 > Python

万万没想到,除了Shannon项目,Python3.11还有这么多性能提升!

时间:2023-03-25 21:24:30 Python

众所周知,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<>shift。comb_odd_part可以通过算术模2**64高效计算,使用三个查找和两个uint64_t乘法,而必要的移位可以通过Kummer定理计算:它是将k添加到n-k二进制时的进位数,这反过来是n^k^(n-k)的集合位数。*另一个改进是,以前用于计算两个除法math.comb(n,k)(对于小n)的最大幂的基于popcount的代码已被基于计算所涉及阶乘的尾随零的更直接的方法所取代。(*).Python3.10:$python-mpyperftimeit-s\'importmath'--'math.comb(100,55)'........................均值+-stddev:3.72us+-0.07us#---$python-mpyperftimeit-s\'importmath'--'math.comb(10000,5500)'.................平均+-标准偏差:11.9毫秒+-0.1毫秒Python3.11:$python-mpyperftimeit-s\'importmath'--'math.comb(100,55)'...........平均+-标准偏差:476ns+-20ns#---$python-mpyperftimeit-s\'importmath'--'math.comb(10000,5500)'................均值+-标准差:2.28毫秒+-0.10毫秒对于统计库:优化均值(数据)、方差(数据、xbar=无)和标准差(数据、xbar=无)3.11优化了统计模块中的均值、方差和标准差函数。如果入参是迭代器,则直接用于计算,不先转成列表。这种计算方法比前一种方法快一倍。*Python3.10:#Mean$python-mpyperftimeit-s\'importstatistics'--'statistics.mean(range(1_000))'........................均值+-标准偏差:255us+-11us#Variance$python-mpyperftimeit-s\'importstatistics'--'statistics.variance((x*0.1forxinrange(0,10)))'......................均值+-stddev:77.0us+-2.9us#样本标准偏差(stdev)$python-mpyperftimeit-s\'导入统计数据'--'statistics.stdev((x*0.1forxinrange(0,10)))'.....................Mean+-stddev:78.0us+-2.2usPython3.11:#Mean$python-mpyperftimeit-s\'importstatistics'--'statistics.mean(range(1_000))'......................均值+-标准差:193us+-7us#Variance$python-mpyperftimeit-s\'importstatistics'--'statistics.variance((x*0.1forxinrange(0,10)))'......................均值+-标准差:56.1us+-2.3us#样本标准差(stdev)$python-mpyperftimeit-s\'importstatistics'--'statistics.stdev((x*0.1forxinrange(0,10)))'.........Mean+-stddev:59.4us+-2.6usunicodedata.normalize()对于纯ASCII字符串,提升为常数时间对于unicodedata.normalize()方法,如果提供的输入参数是纯ASCII字符串,会通过unicode快速校验算法快速返回结果。此检查使用PyUnicode_IS_ASCII实现。Python3.10:$python-mpyperftimeit-s\'importunicodedata'--'unicodedata.normalize("NFC","python")'.........................平均+-标准偏差:83.3ns+-4.3nsPython3.11:$python-mpyperftimeit-s\'importunicodedata'--'unicodedata.normalize("NFC","python")'............Mean+-stddev:34.2ns+-1.2ns结语:我写这篇文章是为了加深对Python3.11最新成果的理解。如果有什么问题,请通过电子邮件或Twitter告诉我。(译注:本次翻译是为了促进大家自己的学习,加强理解,如有错误,敬请指正!)附上HackerNews上的评论,下一篇会分析HackerNews带来的优化点更快的CPython项目。敬请关注!