[英]Is “x < y < z” faster than “x < y and y < z”?
從這個頁面 ,我們知道:
鏈式比較比使用
and
運算符更快。 寫x < y < z
而不是x < y and y < z
。
但是,我得到了不同的結果測試以下代碼片段:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
似乎x < y and y < z
比x < y < z
快。 為什么?
在搜索了這個網站上的一些帖子之后(就像這個一樣),我知道“僅評估一次”是x < y < z
的關鍵,但我仍然感到困惑。 為了進一步研究,我使用dis.dis
反匯編了這兩個函數:
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
輸出是:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
似乎x < y and y < z
具有比x < y < z
更少的拆卸命令。 我應該考慮x < y and y < z
比x < y < z
快嗎?
在Intel(R)Xeon(R)CPU E5640 @ 2.67GHz上使用Python 2.7.6進行測試。
區別在於x < y < z
y
僅評估一次。 如果y是一個變量,這不會產生很大的差異,但是當它是一個函數調用時會這樣做,這需要一些時間來計算。
from time import sleep
def y():
sleep(.2)
return 1.3
%timeit 1.2 < y() < 1.8
10 loops, best of 3: 203 ms per loop
%timeit 1.2 < y() and y() < 1.8
1 loops, best of 3: 405 ms per loop
您定義的兩個函數的最佳字節碼
0 LOAD_CONST 0 (None)
3 RETURN_VALUE
因為沒有使用比較的結果。 讓我們通過返回比較結果使情況更有趣。 讓我們在編譯時也不知道結果。
def interesting_compare(y):
x = 1.1
z = 1.3
return x < y < z # or: x < y and y < z
同樣,兩個版本的比較在語義上是相同的,因此兩個構造的最佳字節碼是相同的。 最好我可以解決它,它看起來像這樣。 我用每個操作碼之前和之后的堆棧內容注釋每一行,用Forth表示法(右邊的堆棧頂部, --
前后划分,尾隨?
表示可能存在或可能不存在的內容)。 請注意, RETURN_VALUE
會丟棄在返回值下面的堆棧上發生的所有事情。
0 LOAD_FAST 0 (y) ; -- y
3 DUP_TOP ; y -- y y
4 LOAD_CONST 0 (1.1) ; y y -- y y 1.1
7 COMPARE_OP 4 (>) ; y y 1.1 -- y pred
10 JUMP_IF_FALSE_OR_POP 19 ; y pred -- y
13 LOAD_CONST 1 (1.3) ; y -- y 1.3
16 COMPARE_OP 0 (<) ; y 1.3 -- pred
>> 19 RETURN_VALUE ; y? pred --
如果語言的實現,CPython,PyPy,無論如何,都不會為這兩種變體生成這個字節碼(或它自己的等效操作序列), 這表明該字節碼編譯器的質量很差 。 從你發布到上面的字節碼序列獲取是一個解決的問題(我認為你需要的是這種情況下的常量折疊 , 死代碼消除 ,以及更好的堆棧內容建模; 常見的子表達式消除也很便宜且有價值),並沒有理由不在現代語言實現中這樣做。
現在,恰好該語言的所有當前實現都具有質量差的字節碼編譯器。 但你應該在編碼時忽略它! 假裝字節碼編譯器是好的,並編寫最可讀的代碼。 無論如何,它可能足夠快。 如果不是,請首先尋找算法改進,然后再嘗試Cython - 這將為您提供比您可能應用的任何表達式調整更多的改進。
由於輸出的差異似乎是由於缺乏優化,我認為在大多數情況下你應該忽略這種差異 - 可能差異會消失。 區別在於因為y
只應該被評估一次,並且通過在堆棧上復制它來解決,這需要額外的POP_TOP
- 盡管可能使用LOAD_FAST
的解決方案。
但重要的區別是,在x<y and y<z
,如果x<y
計算結果為真,則第二個y
應該被評估兩次,如果對y
的評估需要相當長的時間或有副作用,則會產生影響。
在大多數情況下,你應該使用x<y<z
盡管它有點慢。
首先,你的比較幾乎毫無意義,因為沒有引入兩種不同的結構來提供性能改進,所以你不應該根據它來決定是否使用一種結構代替另一種。
x < y < z
構造:
x
, y
和z
一次,並檢查整個條件是否成立。 通過多次計算y
使用and
更改語義,這可以改變結果 。 因此,根據您想要的語義選擇一個代替另一個, 如果它們是等價的,那么一個是否比另一個更可讀。
這表示:更多的反匯編代碼也並不意味着慢的代碼。 但是,執行更多的字節碼操作意味着每個操作都更簡單,但它需要主循環的迭代。 這意味着如果您正在執行的操作非常快(例如,當您在那里執行局部變量查找)時,執行更多字節碼操作的開銷可能很重要。
但是請注意,這個結果不會在更一般的情況下舉行,只到你碰巧配置文件中的“最壞情況”。 正如其他人所指出的那樣,如果你將y
改為需要更多時間的東西,你會看到結果發生變化,因為鏈式符號只會評估一次。
總結:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.