Оптимизация кода Python с помощью dis и timeit

Я пытался создать функцию с учетом производительности и ужасно потерпел неудачу ( функция 1).

Рассмотрим функцию, в которой нам нужно преобразовать строку в целое число с заданными правилами:

B = отрицать целое число
O = прибавлять + 1 к целому числу
Никакой другой символ, кроме B или O, никогда не будет указан.
B - всегда префикс, O - всегда постфикс.

функция 1

      def calculate_floor(floor: str) -> int:
    return eval(floor.replace('B', '-').replace('O', '+1'))

функция 2

      import re

def calculate_floor(floor: str) -> int:
    int_floor = int(re.sub('[a-zA-Z]', '', floor))
    if floor[0] == 'B':
        int_floor *= -1
    if floor[-1] == 'O':
        int_floor += 1
    return int_floor

байт-код функции 1 и производительность

      python -m timeit -n 2000000 'import test' 'test.calculate_floor("B15O")'
2000000 loops, best of 5: 6.48 usec per loop

$ python -m dis test.py 
  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (re)
              6 STORE_NAME               0 (re)

  3           8 LOAD_NAME                1 (str)
             10 LOAD_NAME                2 (int)
             12 LOAD_CONST               2 (('floor', 'return'))
             14 BUILD_CONST_KEY_MAP      2
             16 LOAD_CONST               3 (<code object calculate_floor at 0x0000025CB47A7500, file "test.py", line 3>)
             18 LOAD_CONST               4 ('calculate_floor')
             20 MAKE_FUNCTION            4 (annotations)
             22 STORE_NAME               3 (calculate_floor)

  6          24 LOAD_NAME                4 (print)
             26 LOAD_NAME                3 (calculate_floor)
             28 LOAD_CONST               5 ('B15O')
             30 CALL_FUNCTION            1
             32 CALL_FUNCTION            1
             34 POP_TOP
             36 LOAD_CONST               1 (None)
             38 RETURN_VALUE

Disassembly of <code object calculate_floor at 0x0000025CB47A7500, file "test.py", line 3>:
  4           0 LOAD_GLOBAL              0 (eval)
              2 LOAD_FAST                0 (floor)
              4 LOAD_METHOD              1 (replace)
              6 LOAD_CONST               1 ('B')
              8 LOAD_CONST               2 ('-')
             10 CALL_METHOD              2
             12 LOAD_METHOD              1 (replace)
             14 LOAD_CONST               3 ('O')
             16 LOAD_CONST               4 ('+1')
             18 CALL_METHOD              2
             20 CALL_FUNCTION            1
             22 RETURN_VALUE

байт-код функции 2 и производительность

      $ python -m timeit -n 2000000 'import test' 'test.calculate_floor("B15O")'
2000000 loops, best of 5: 1.84 usec per loop

$ python -m dis test.py 
  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (re)
              6 STORE_NAME               0 (re)

  3           8 LOAD_NAME                1 (str)
             10 LOAD_NAME                2 (int)
             12 LOAD_CONST               2 (('floor', 'return'))
             14 BUILD_CONST_KEY_MAP      2
             16 LOAD_CONST               3 (<code object calculate_floor at 0x00000233EE3A7450, file "test.py", line 3>)
             18 LOAD_CONST               4 ('calculate_floor')
             20 MAKE_FUNCTION            4 (annotations)
             22 STORE_NAME               3 (calculate_floor)

 11          24 LOAD_NAME                4 (print)
             26 LOAD_NAME                3 (calculate_floor)
             28 LOAD_CONST               5 ('B15O')
             30 CALL_FUNCTION            1
             32 CALL_FUNCTION            1
             34 POP_TOP
             36 LOAD_CONST               1 (None)
             38 RETURN_VALUE

Disassembly of <code object calculate_floor at 0x00000233EE3A7450, file "test.py", line 3>:
  4           0 LOAD_GLOBAL              0 (int)
              2 LOAD_GLOBAL              1 (re)
              4 LOAD_METHOD              2 (sub)
              6 LOAD_CONST               1 ('[a-zA-Z]')
              8 LOAD_CONST               2 ('')
             10 LOAD_FAST                0 (floor)
             12 CALL_METHOD              3
             14 CALL_FUNCTION            1
             16 STORE_FAST               1 (int_floor)

  5          18 LOAD_FAST                0 (floor)
             20 LOAD_CONST               3 (0)
             22 BINARY_SUBSCR
             24 LOAD_CONST               4 ('B')
             26 COMPARE_OP               2 (==)
             28 POP_JUMP_IF_FALSE       38

  6          30 LOAD_FAST                1 (int_floor)
             32 LOAD_CONST               5 (-1)
             34 INPLACE_MULTIPLY
             36 STORE_FAST               1 (int_floor)

  7     >>   38 LOAD_FAST                0 (floor)
             40 LOAD_CONST               5 (-1)
             42 BINARY_SUBSCR
             44 LOAD_CONST               6 ('O')
             46 COMPARE_OP               2 (==)
             48 POP_JUMP_IF_FALSE       58

  8          50 LOAD_FAST                1 (int_floor)
             52 LOAD_CONST               7 (1)
             54 INPLACE_ADD
             56 STORE_FAST               1 (int_floor)

  9     >>   58 LOAD_FAST                1 (int_floor)
             60 RETURN_VALUE

Байт-коды немного отличаются от примеров, поскольку в них также включен оператор print (calculate_floor('B150')).

Мои вопросы:

Почему функция 1 намного медленнее, чем функция 2 (в 5 раз). Это потому, что вызовы функций дороги ( функция 1 имеет 3 вызова функций, а функция 2 имеет 2 вызова функций). Это базовая реализация eval () и replace ()? или сочетание этих двух причин?

Какое решение этой проблемы было бы более оптимизированным с точки зрения производительности?

1 ответ

Предположительно, самым быстрым простым решением Python будет просто набор вложенных операторов if, охватывающих четыре варианта (1, B1, 1O, B1O):

      def calculate_floor(floor: str) -> int:
    if floor[0] == 'B':
      if floor[-1] == 'O':
        return -int(floor[1:-1]) + 1
      else:
        return -int(floor[1:])
    else:
      if floor[-1] == 'O':
        return int(floor[:-1]) + 1
      else:
        return int(floor)
Другие вопросы по тегам