Оптимизация кода 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)