每種編程語(yǔ)言為了表現(xiàn)出色,并且實(shí)現(xiàn)卓越的性能,都需要大量編譯器級(jí)的優(yōu)化。
一種著名的優(yōu)化技術(shù)是“常量折疊”(Constant Folding):在編譯期間,編譯器會(huì)設(shè)法識(shí)別出常量表達(dá)式,對(duì)其進(jìn)行求值,然后用求值的結(jié)果來(lái)替換表達(dá)式,從而使得運(yùn)行時(shí)更精簡(jiǎn)。
在本文中,我們深入探討了什么是常量折疊,了解了它在 Python 世界中的適用范圍,最后解讀了 Python 的源代碼(即 CPython),并分析出 Python 是如何優(yōu)雅地實(shí)現(xiàn)它。
常量折疊
所謂常量折疊,指的是在編譯時(shí)就查找并計(jì)算常量表達(dá)式,而不是在運(yùn)行時(shí)再對(duì)其進(jìn)行計(jì)算,從而會(huì)使運(yùn)行時(shí)更加精簡(jiǎn)和快速。
>> > day_sec = 24 * 60 * 60
當(dāng)編譯器遇到一個(gè)常量表達(dá)式時(shí),如上所述,它將對(duì)表達(dá)式求值,并作替換。
通常而言,表達(dá)式會(huì)被“抽象語(yǔ)法樹(shù)”( Abstract Syntax Tree,簡(jiǎn)寫(xiě)為 AST)中的計(jì)算值所替換,但是這完全取決于語(yǔ)言的實(shí)現(xiàn)。
因此,上述表達(dá)式可以等效地被執(zhí)行為:
>> > day_sec = 86400
Python 中的常量折疊
在 Python 中,我們可以使用反匯編模塊(Disassembler)獲取 CPython 字節(jié)碼,從而更好地了解代碼執(zhí)行的過(guò)程。
當(dāng)使用dis模塊反匯編上述常量表達(dá)式時(shí),我們會(huì)得到以下字節(jié)碼:
>> > import dis
>> > dis.dis("day_sec = 24 * 60 * 60")
0 LOAD_CONST 0 (86400)
2 STORE_NAME 0 (day_sec)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
從字節(jié)碼中可以看出,它只有一個(gè)LOAD_CONST ,以及一個(gè)已經(jīng)計(jì)算好的值86400。
這表明 CPython 解釋器在解析和構(gòu)建抽象語(yǔ)法樹(shù)期間,會(huì)折疊常量表達(dá)式 24 * 60 * 60,并將其替換為計(jì)算值 86400。
常量折疊的適應(yīng)范圍
Python 會(huì)嘗試折疊每一個(gè)常量表達(dá)式,但在某些情況下,即使該表達(dá)式是常量,但是 Python 并不會(huì)對(duì)其進(jìn)行折疊。
例如,Python 不會(huì)折疊x = 4 ** 64,但會(huì)折疊 x = 2 ** 64。

除了算術(shù)表達(dá)式,Python 還會(huì)折疊涉及字符串和元組的表達(dá)式,其中,長(zhǎng)度不超過(guò) 4096 的字符串常量表達(dá)式會(huì)被折疊。
>> > a = "-" * 4096 # folded
>> > a = "-" * 4097 # not folded
>> > a = "--" * 4096 # not folded
常量折疊的內(nèi)部細(xì)節(jié)
現(xiàn)在,我們將重點(diǎn)轉(zhuǎn)移到內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),即關(guān)注 CPython 在哪里以及如何實(shí)現(xiàn)常量折疊。
所有的 AST 優(yōu)化(包括常量折疊)都可以在 ast_opt.c 文件中找到?;镜拈_(kāi)始函數(shù)是 astfold_expr,它會(huì)折疊 Python 源碼中包含的所有表達(dá)式。
這個(gè)函數(shù)以遞歸方式遍歷 AST,并試著折疊每個(gè)常量表達(dá)式,如下面的代碼片段所示:

astfold_expr 在折疊某個(gè)表達(dá)式之前,會(huì)嘗試折疊其子表達(dá)式(操作對(duì)象),然后將折疊操作代理給特定的表達(dá)式折疊函數(shù)。
特定操作的折疊函數(shù)對(duì)表達(dá)式求值,并返回計(jì)算后的常數(shù),然后將其放入 AST 中。
例如,每當(dāng) astfold_expr 遇到二值運(yùn)算時(shí),它便調(diào)用 fold_binop,遞歸地計(jì)算兩個(gè)子操作對(duì)象(表達(dá)式) 。
fold_binop 函數(shù)返回計(jì)算后的常量值,如下面的代碼片段所示:

fold_binop 函數(shù)通過(guò)檢查當(dāng)前運(yùn)算符的種類(lèi),然后調(diào)用其相應(yīng)的處理函數(shù)來(lái)折疊二值運(yùn)算。例如,如果當(dāng)前的操作是加法運(yùn)算,為了計(jì)算最終值,它會(huì)對(duì)其左側(cè)和右側(cè)操作數(shù)調(diào)用 PyNumber_Add。
怎樣優(yōu)雅?
為了有效地折疊某些模式或類(lèi)型的常量表達(dá)式,CPython 不會(huì)寫(xiě)特殊的邏輯,而是調(diào)用相同的通用代碼。例如,在折疊時(shí),它會(huì)調(diào)用通用的 PyNumber_Add 函數(shù),跟執(zhí)行常規(guī)的加法操作一樣。
因此,CPython 通過(guò)確保其通用代碼/計(jì)算過(guò)程可以處理常量表達(dá)式的求值,從而消除了編寫(xiě)特殊函數(shù)來(lái)處理常量折疊的需要。
-
模塊
+關(guān)注
關(guān)注
7文章
2837瀏覽量
53294 -
字符串
+關(guān)注
關(guān)注
1文章
596瀏覽量
23166 -
編譯器
+關(guān)注
關(guān)注
1文章
1672瀏覽量
51618 -
python
+關(guān)注
關(guān)注
57文章
4876瀏覽量
90041
發(fā)布評(píng)論請(qǐng)先 登錄
【AI學(xué)習(xí)】第4篇--Python編輯入門(mén)
2.1 python常量與變量
Python的常量與變量基礎(chǔ)知識(shí)詳解
讓你的 Python 代碼優(yōu)雅又地道
python基礎(chǔ)教程之python入門(mén)教程中文手冊(cè)免費(fèi)下載
Python入門(mén)之Python基礎(chǔ)教程資料免費(fèi)下載
Python學(xué)習(xí)手冊(cè)免費(fèi)下載
Python學(xué)習(xí)手冊(cè)免費(fèi)下載
Python更優(yōu)雅的日志記錄解決方案Loguru
Python實(shí)現(xiàn)所有算法-基本牛頓法
Python的優(yōu)雅之處:Descriptor(描述符)
什么是常量折疊?Python如何優(yōu)雅地實(shí)現(xiàn)它
評(píng)論