[python] “x <y <z”가 “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합니까?

2.67GHz @ Intel Xeon CPU E5640에서 Python 2.7.6으로 테스트되었습니다.



답변

차이점은 in 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 표기법에서 각 opcode 전후에 스택 내용으로 각 줄에 주석을 달았습니다 (오른쪽 스택의 맨 위, --전후의 구분, 후행 ?은있을 수도 있고 없을 수도 있음). 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은 두 번째 평가에서 true y로 평가되면 두 번 평가되어야한다는 것 x<y입니다.y 상당한 시간 걸리거나 부작용이있는 있습니다.

대부분의 시나리오에서는 x<y<z다소 느리다는 사실에도 불구하고 사용해야합니다 .


답변

우선 , 성능 향상을 위해 두 개의 서로 다른 구성이 도입 되지 않았기 때문에 비교는 의미가 없습니다 .

x < y < z구조 :

  1. 그 의미에서 더 명확하고 직접적입니다.
  2. evalute을 : 그것의 의미는 당신이 비교의 “수학적 의미”에서 기대할 수있는 것입니다 x, y그리고 z 한 번 전체 상태가 보유하고 있는지 확인하세요. 를 사용하면 여러 번 and평가하여 의미가 변경되어 결과y변경 될 수 있습니다 .

그래서, 당신이 원하는 의미에 따라 다른 장소에서 하나를 선택 하는 경우 가 동일 하나가 다른 것보다 더 읽을 수 있는지 여부.

더 많은 분해 된 코드가 느린 코드를 의미하지는 않습니다 . 그러나 더 많은 바이트 코드 작업을 실행하면 각 작업이 더 단순하지만 기본 루프를 반복해야합니다. 이러한 것이 의미 하는 경우 가 수행하는 동작은 (예를 들어 로컬 변수 조회가 존재하고있는 바와 같이) 매우 신속하고, 그 오버 더 바이트 동작을 실행 중요 할 수있다.

그러나이 결과는 않습니다 하지 만 프로파일 링하는 일이있는 “최악의 경우”에,보다 일반적인 상황에서 개최. 다른 사람들이 언급했듯이, 변경하면y 조금 더 시간이 걸리는 것으로 변경하면 체인 표기법이 한 번만 평가하기 때문에 결과가 변경되는 것을 볼 수 있습니다.

요약 :

  • 성능 전에 의미를 고려하십시오.
  • 가독성을 고려하십시오.
  • 마이크로 벤치 마크를 신뢰하지 마십시오. 함수 / 표현 타이밍이 상기 파라미터와 관련하여 동작하는 방법을보고 사용 방법을 고려하려면 항상 다른 종류의 파라미터로 프로파일 링하십시오.

답변