簡體   English   中英

“x <y <z”比“x <y和y <z”快嗎?

[英]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 < zx < 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 < zx < 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構造:

  1. 它的含義更清晰,更直接。
  2. 它的語義是你對比較的“數學意義”所期望的:evalute xyz 一次,並檢查整個條件是否成立。 通過多次計算y使用and更改語義,這可以改變結果

因此,根據您想要的語義選擇一個代替另一個, 如果它們是等價的,那么一個是否比另一個更可讀。

這表示:更多的反匯編代碼也並不意味着慢的代碼。 但是,執行更多的字節碼操作意味着每個操作都更簡單,但它需要主循環的迭代。 這意味着如果您正在執行的操作非常快(例如,當您在那里執行局部變量查找)時,執行更多字節碼操作的開銷可能很重要。

但是請注意,這個結果不會在更一般的情況下舉行,只到你碰巧配置文件中的“最壞情況”。 正如其他人所指出的那樣,如果你將y改為需要更多時間的東西,你會看到結果發生變化,因為鏈式符號只會評估一次。

總結:

  • 在性能之前考慮語義。
  • 考慮可讀性。
  • 不要相信微基准。 始終使用不同類型的參數進行分析,以查看函數/表達式時序與所述參數的關系,並考慮您計划如何使用它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM