Python311新特性-特化指令specializing adaptive interpreter-typing-asyncio

赵青青發表於2024-11-03

Python3新特性

python3.11增加了許多特性,讓python更快更加安全,本文從應用層面來講一下python3.11的這些新特性

特化自適應解析器是什麼,如何利用特化寫出更高效能的程式碼

如何在專案中落地type hint寫出健壯的程式碼,有那些注意事項

asyncio的概念及應用場景

Faster Python 3.11

Faster

  1. Zero cost exception(if not thrown)
  2. 10% faster re & atomic grouping, possessive qualifiers
  3. 10-15% faster startup
  4. Faster function calls
  5. C-style formatting sometimes as fast as f-string
  6. Less memory for string keys in dicts
  7. Specialized adaptive interpreter
  8. And more!

Future

  1. Major focus for the next several releases
  2. Simple JIT planned eventually
  3. The main driver behind C API changes

Specializing(特化)

當一個函式被執行的次數足夠多(>53)就會被特化,被特化的指令叫hot code。(次數需要看對應不同版本cpython原始碼)

原始碼:Python/specialize.c -> _PyCode_Warmup

特化流程:

​ 原始指令 —— 中間狀態(名稱中含ADAPTIVE) —— 特化後的指令(非常快)

img

圖片來源:Python3-原始碼剖析(二)-指令特化 | Yuerer's Blog

碰到問題

同樣的程式碼在命令列中可以被特化,而放一個.py檔案中,再透過dis.dis(module.func,adaptive=True)就無法被特化

示例函式程式碼如下:

>>> def f(x):
...     return x*x
... for i in range(100):
...     f(i)

image-20241028155628526

解釋:乘法的opcode為BINARY_OP,在這個例子中我們傳的是int當被特化後會變成BINARY_OP_MULTIPLY_INT,因為python弱型別,確定的型別可以極大提高速度,建議去看cpython的實現原始碼加深理解。

把上面程式碼放在.py檔案中,發現無法進行特化

>>> dis.dis(adaptiveTest.f,adaptive=True)
 10           RESUME                   0

 11           LOAD_FAST                0 (x)
              LOAD_CONST               1 (2)
              BINARY_OP                5 (*)
              RETURN_VALUE

最終找到原因:我在vscode自帶的終端import之後,在執行時修改了py程式碼,沒有重新reload,導致沒有載入最新的程式碼(py3的reload和py2有區別)。

另一個方法就是:重新開啟windows的cmd中並執行一遍

image-20241028161816185

還有一種方法就是:稍稍調整一下程式碼,把dis加到.py中,然後執行python檔案也可以看到函式被特化

import dis
def foo(x):
	return x*x

for i in range(100):
	foo(i)

dis.dis(foo,adaptive=True)
#在python中呼叫dis列印出位元組碼

LOAD_ATTR(getattr)特化

self.xx 本質就是getattr,對應的opcode為 LOAD_ATTR,在python3中預設可以被特化,例如:

  1. 繼承object的原生Python類可以特化

  2. 繼承後object重寫 __getattr__ 的Python 類無法特化

  3. C 擴充套件 Python 類無法特化

為什麼後面2種不能完成特化?

class B(object):
	def __getattr__(self, name):#重寫__getattr__
		return super(B, self).__getattr__(name)

b = B()
b.x = 1
def mytest(n):
	for i in range(n):
		b.x #無法被特化

因為:cpython中特化前判斷是否為原始的getattr函式,見:Python\specialize.c

image-20241103104311246

image-20241103104308223

如何讓C擴充套件python類可以特化?

重點講解:2種實現方法

  1. 在c擴充套件類中增加cache儲存下標
  2. 修改虛擬機器的實現,傳入下標

如何檢查程式碼是否被特化?

視覺化特化工具,github:https://github.com/brandtbucher/specialist

執行程式碼並生成(網頁)報表,那麼如何納入到專案中進行視覺化呢?因為遊戲專案依賴於引擎API,需要跑在遊戲引擎之上,不同於純python環境

Typing check(type hint)

base vscode Pylance

Type Ignore

pyrightconfig.json 相容py2的檔案,忽略整個檔案

overload

配合vscode的pylance特性來做程式碼檢查

當函式傳參個數不符合要求時,在IDE中進行報錯提示

Stub Files

和py同名的檔案格式為.pyi,語法也一樣,在這裡寫type hint,提供給IDE使用,執行時無關

AsyncIO

What is it?

Keywords pair(async / await)

So what?

What is it?

Asyncio is used as a foundation for multiple Python asynchronous frameworks that
provide high-performance network and web-servers, database connection libraries,
distributed task queues, etc.
Asyncio is often a perfect fit for IO-bound and high-level structured network code.

簡單的例子發揮不出作用

import asyncio
async def foo():
	await asyncio.sleep(1)
	print ('foo')
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(foo())

上面這個簡單的asyncio的例子和下面這段程式碼作用一樣,無法體現出asyncio的作用

def foo():
	time.sleep(1)
	print('foo')
foo()

適合用在那些地方?

Patch

  • Simultaneously download multiple block of patch

Distributed Task Framework

  • Multi-Process Management through ProcessPoolExecutor
  • Export-table-tools
  • Texture Compressor
  • build packer

感興趣的可以搜尋ProcessPoolExecutor去了解

UVLoop

uvloop用來替換asyncio的event loop更高效,底層使用libuv透過cython實現,比原生的asyncio快2~4倍,有線上專案已驗證過其穩定性

開源地址:https://github.com/MagicStack/uvloop

簡單幾行就可以替換asyncio的event loop

import asyncio
import sys

import uvloop

async def main():
    # Main entry-point.
    ...

if sys.version_info >= (3, 11):
    with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
        runner.run(main())
else:
    uvloop.install()
    asyncio.run(main())