Почему вызов float() на число медленнее, чем добавление 0.0 в Python?
В чем причина того, что приведение целого числа в float медленнее, чем добавление 0.0 к этому int в Python?
import timeit
def add_simple():
for i in range(1000):
a = 1 + 0.0
def cast_simple():
for i in range(1000):
a = float(1)
def add_total():
total = 0
for i in range(1000):
total += 1 + 0.0
def cast_total():
total = 0
for i in range(1000):
total += float(1)
print "Add simple timing: %s" % timeit.timeit(add_simple, number=1)
print "Cast simple timing: %s" % timeit.timeit(cast_simple, number=1)
print "Add total timing: %s" % timeit.timeit(add_total, number=1)
print "Cast total timing: %s" % timeit.timeit(cast_total, number=1)
Выходной сигнал которого:
Добавить простую синхронизацию: 0.0001220703125
Простая синхронизация: 0.000469923019409
Добавить общее время: 0.000164985656738
Общее время: 0.00040078163147
Ответы
Ответ 1
Если вы используете модуль dis
, вы можете начать понимать, почему:
In [11]: dis.dis(add_simple)
2 0 SETUP_LOOP 26 (to 29)
3 LOAD_GLOBAL 0 (range)
6 LOAD_CONST 1 (1000)
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 GET_ITER
>> 13 FOR_ITER 12 (to 28)
16 STORE_FAST 0 (i)
3 19 LOAD_CONST 4 (1.0)
22 STORE_FAST 1 (a)
25 JUMP_ABSOLUTE 13
>> 28 POP_BLOCK
>> 29 LOAD_CONST 0 (None)
32 RETURN_VALUE
In [12]: dis.dis(cast_simple)
2 0 SETUP_LOOP 32 (to 35)
3 LOAD_GLOBAL 0 (range)
6 LOAD_CONST 1 (1000)
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 GET_ITER
>> 13 FOR_ITER 18 (to 34)
16 STORE_FAST 0 (i)
3 19 LOAD_GLOBAL 1 (float)
22 LOAD_CONST 2 (1)
25 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
28 STORE_FAST 1 (a)
31 JUMP_ABSOLUTE 13
>> 34 POP_BLOCK
>> 35 LOAD_CONST 0 (None)
38 RETURN_VALUE
Обратите внимание на CALL_FUNCTION
Вызов функций в Python (относительно) медленный. Как и поиски .
. Поскольку для функции float
требуется вызов функции - почему она медленнее.
Ответ 2
Если вы посмотрите на байт-код для add_simple
:
>>> dis.dis(add_simple)
2 0 SETUP_LOOP 26 (to 29)
3 LOAD_GLOBAL 0 (range)
6 LOAD_CONST 1 (1000)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 12 (to 28)
16 STORE_FAST 0 (i)
3 19 LOAD_CONST 4 (1.0)
22 STORE_FAST 1 (a)
25 JUMP_ABSOLUTE 13
>> 28 POP_BLOCK
>> 29 LOAD_CONST 0 (None)
32 RETURN_VALUE
Вы увидите, что 0.0
на самом деле нигде нет. Он просто загружает константу 1.0
и сохраняет ее на a
. Python вычислил результат во время компиляции, так что вы на самом деле не синхронизируете добавление.
Если вы используете переменную для 1
, поэтому оптимизатор Python для подголовника не может выполнять добавление во время компиляции, добавив 0.0
еще впереди:
>>> timeit.timeit('float(a)', 'a=1')
0.22538208961486816
>>> timeit.timeit('a+0.0', 'a=1')
0.13347005844116211
Вызов float
требует двух запросов dict, чтобы выяснить, что такое float
, одно в глобальном пространстве имен модуля и одно во встроенных. Он также имеет служебную нагрузку на функцию функции Python, которая дороже, чем вызов функции C.
Для добавления 0.0
требуется только индексирование в объект-код функции co_consts
для загрузки константы 0.0
, а затем вызов функций nb_add
уровня C типов int
и float
для выполнения дополнение. Это более низкий объем накладных расходов.
Ответ 3
Если вы используете Python 3 или недавнюю версию Python 2 (2.5 или выше), она постоянно складывается в время генерации байт-кода. Это означает, что 1 + 0.0
заменяется на 1.0
перед выполнением кода.
Ответ 4
Выполнение добавления может быть выполнено в C. Выполнение приведения вызывает функцию, которая будет вызываться, а затем C lib для ввода. Там накладные расходы для этого вызова функции.
Ответ 5
Проще говоря, вы ничего не бросаете. Листинг типа подсказывает компилятору обрабатывать значение в переменной, как если бы у него был другой тип; используются одни и те же базовые разряды. Однако Python float(1)
создает новый объект в памяти, отличный от аргумента float
.
Когда вы добавляете 1 + 0.0
, это просто вызывает (1).__add__(0.0)
, а метод __add__
встроенного класса int
знает, как обращаться с объектами float
. Никаких дополнительных объектов (кроме возвращаемого значения) не требуется.
В новых версиях Python есть оптимизатор, так что постоянные выражения типа 1 + 0.0
можно заменить во время компиляции 1.0
; никакие функции не должны выполняться во время выполнения. Замените 1 + 0.0
на x = 1
(до цикла) и x + 0.0
, чтобы заставить int.__add__
вызывать во время выполнения, чтобы наблюдать разницу. Он будет медленнее, чем 1 + 0.0
, но все же быстрее, чем float(1)
.