dis 모듈을 활용해서 바이트코드 분석하기

파이썬 코드 리뷰 꿀팁, 김동현 - PyCon Korea 2022 [https://youtu.be/dzp0-5lInw0]
[파이썬] dis모듈로 바이트 코드 확인 - dis - [https://aia1235.tistory.com/60]

def func():
    a = 1
    b = 2
    c = a + b
    return c

- 바이트코드에 익숙하지 않다면 이해하기 어렵다

print(func.__code__.co_code)
print(list(func.__code__.co_code))

- output -

b'\x97\x00d\x01}\x00d\x02}\x01|\x00|\x01z\x00\x00\x00}\x02|\x02S\x00'
[151, 0, 100, 1, 125, 0, 100, 2, 125, 1, 124, 0, 124, 1, 122, 0, 0, 0, 125, 2, 124, 2, 83, 0]

- 이럴 때 사용할 수 있는 모듈이 dis

import dis
import timeit
dis.dis(func)

- output -

1           0 RESUME                   0

2           2 LOAD_CONST               1 (1)
            4 STORE_FAST               0 (a)

3           6 LOAD_CONST               2 (2)
            8 STORE_FAST               1 (b)

4          10 LOAD_FAST                0 (a)
           12 LOAD_FAST                1 (b)
           14 BINARY_OP                0 (+)
           18 STORE_FAST               2 (c)

5          20 LOAD_FAST                2 (c)
           22 RETURN_VALUE

- 리스트 값 확인

[151, 0, 100, 1, 125, 0, 100, 2, 125, 1, 124, 0, 124, 1, 122, 0, 0, 0, 125, 2, 124, 2, 83, 0]

print(f'151: {dis.opname[125]}')
print(f'100: {dis.opname[100]}')
print(f'125: {dis.opname[125]}')

- output -

151: STORE_FAST
100: LOAD_CONST
125: STORE_FAST

- 더 쉽게 보고 싶다면

dis.show_code(func)

- output -

Name:              func
Filename:          /tmp/ipykernel_30725/21688586.py
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  3
Stack size:        2
Flags:             OPTIMIZED, NEWLOCALS
Constants:
   0: None
   1: 1
   2: 2
Variable names:
   0: a
   1: b
   2: c




사용 예시

어떤게 더 효율적인지 비교

ee={}
ee['3'] = 32
ee
ew={}
ew.update({'3': 32})
ew

둘 다 {‘3’: 32} 반환

# 서브스크립션
dis.dis('d[k] = v')

- output -

0           0 RESUME                   0

1           2 LOAD_NAME                0 (v)
            4 LOAD_NAME                1 (d)
            6 LOAD_NAME                2 (k)
            8 STORE_SUBSCR
           12 LOAD_CONST               0 (None)
           14 RETURN_VALUE

0 RESUME 0: 이는 코드의 첫 번째 라인을 나타냅니다. RESUME은 실행을 재개하는 지점을 나타내는 명령입니다. 여기서는 실행을 계속 진행합니다.
2 LOAD_NAME 0 (v): 변수 v를 로드합니다.
4 LOAD_NAME 1 (d): 변수 d를 로드합니다.
6 LOAD_NAME 2 (k): 변수 k를 로드합니다.
8 STORE_SUBSCR: 변수 d의 k번째 요소에 변수 v를 할당합니다.
12 LOAD_CONST 0 (None): 상수 None을 로드합니다.
14 RETURN_VALUE: None을 반환하고 코드 실행을 종료합니다.

dis.dis('d.update({k: v})')

- output -

0           0 RESUME                   0

1           2 LOAD_NAME                0 (d)
            4 LOAD_METHOD              1 (update)
           26 LOAD_NAME                2 (k)
           28 LOAD_NAME                3 (v)
           30 BUILD_MAP                1
           32 PRECALL                  1
           36 CALL                     1
           46 RETURN_VALUE

0 RESUME 0: 이는 코드의 첫 번째 라인을 나타냅니다. RESUME은 실행을 재개하는 지점을 나타내는 명령입니다. 여기서는 실행을 계속 진행합니다.
2 LOAD_NAME 0 (d): 변수 d를 로드합니다.
4 LOAD_METHOD 1 (update): update라는 메서드를 로드합니다. 이는 변수 d의 값을 업데이트하는 메서드일 수 있습니다.
26 LOAD_NAME 2 (k): 변수 k를 로드합니다.
28 LOAD_NAME 3 (v): 변수 v를 로드합니다.
30 BUILD_MAP 1: 로드한 변수 k와 v를 사용하여 사전 형태의 매핑을 만듭니다.
32 PRECALL 1: 메서드 호출을 준비합니다.
36 CALL 1: update 메서드를 호출하고, 변수 d에 매개변수로 생성한 매핑을 전달합니다. 이를 통해 변수 d를 업데이트합니다.
46 RETURN_VALUE: 결과값을 반환하고 코드 실행을 종료합니다.

하지만, 각각의 상황에 맞추어서 사용하는 것이 중요




비교 해보기

  • Func_else

    def Func_else(n):
        if n % 2 == 0:
            return True
        else:
            return False
    dis.dis(Func_else)
      
    ## -------------------- output -------------------- ##
    1           0 RESUME                   0
      
    2           2 LOAD_FAST                0 (n)
                4 LOAD_CONST               1 (2)
                6 BINARY_OP                6 (%)
               10 LOAD_CONST               2 (0)
               12 COMPARE_OP               2 (==)
               18 POP_JUMP_FORWARD_IF_FALSE     2 (to 24)
      
    3          20 LOAD_CONST               3 (True)
               22 RETURN_VALUE
      
    5     >>   24 LOAD_CONST               4 (False)
               26 RETURN_VALUE
    ## ------------------------------------------------ ##
    
  • Func_elif

    def Func_elif(n):
        if n % 2 == 0:
            return True
        elif n % 2 == 1:
            return False
    dis.dis(Func_elif)
      
    ## -------------------- output -------------------- ##
    1           0 RESUME                   0
      
    2           2 LOAD_FAST                0 (n)
                4 LOAD_CONST               1 (2)
                6 BINARY_OP                6 (%)
               10 LOAD_CONST               2 (0)
               12 COMPARE_OP               2 (==)
               18 POP_JUMP_FORWARD_IF_FALSE     2 (to 24)
      
    3          20 LOAD_CONST               3 (True)
               22 RETURN_VALUE
      
    4     >>   24 LOAD_FAST                0 (n)
               26 LOAD_CONST               1 (2)
               28 BINARY_OP                6 (%)
               32 LOAD_CONST               4 (1)
               34 COMPARE_OP               2 (==)
               40 POP_JUMP_FORWARD_IF_FALSE     2 (to 46)
      
    5          42 LOAD_CONST               5 (False)
               44 RETURN_VALUE
      
    4     >>   46 LOAD_CONST               0 (None)
               48 RETURN_VALUE
    ## ------------------------------------------------ ##
    
  • Func_if

    def Func_if(n):
        if n % 2 == 0:
            return True
        return False
    dis.dis(Func_if)
      
    ## -------------------- output -------------------- ##
    1           0 RESUME                   0
      
    2           2 LOAD_FAST                0 (n)
                4 LOAD_CONST               1 (2)
                6 BINARY_OP                6 (%)
               10 LOAD_CONST               2 (0)
               12 COMPARE_OP               2 (==)
               18 POP_JUMP_FORWARD_IF_FALSE     2 (to 24)
      
    3          20 LOAD_CONST               3 (True)
               22 RETURN_VALUE
      
    4     >>   24 LOAD_CONST               4 (False)
               26 RETURN_VALUE
    ## ------------------------------------------------ ##
    
  • 전체 실행 시간 측정

    # 실행 시간 측정 함수
    def measure_execution_time(func, n):
        start_time = timeit.default_timer()
        result = func(n)
        end_time = timeit.default_timer()
        execution_time = end_time - start_time
        return execution_time, result
      
    # 실행 시간 측정
    n = 1  # 테스트할 값
      
    # Func_else(n)의 실행 시간 측정
    execution_time_else, result_else = measure_execution_time(Func_else, n)
    print(f"Func_else 실행 시간: {execution_time_else} 초")
    print(f"Func_else 결과: {result_else}")
      
    # Func_elif(n)의 실행 시간 측정
    execution_time_elif, result_elif = measure_execution_time(Func_elif, n)
    print(f"Func_elif 실행 시간: {execution_time_elif} 초")
    print(f"Func_elif 결과: {result_elif}")
      
      
    # Func_if(n)의 실행 시간 측정
    execution_time_if, result_if = measure_execution_time(Func_if, n)
    print(f"Func_if 실행 시간: {execution_time_if} 초")
    print(f"Func_if 결과: {result_if}")
    

    - result -

    Func_else 실행 시간: 1.989305019378662e-06 초
    Func_else 결과: False
    Func_elif 실행 시간: 1.5348196029663086e-06 초
    Func_elif 결과: False
    Func_if 실행 시간: 1.4528632164001465e-06 초
    Func_if 결과: False
    

댓글남기기