Подтвердить что ты не робот

Почему вызов 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

4b9b3361

Ответ 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).