Почему 2 * x * x быстрее, чем 2 * (x * x) в Python 3.x, для целых чисел? - PullRequest
0 голосов
/ 01 декабря 2018

Следующее целочисленное умножение Python 3.x занимает в среднем от 1,66 до 1,77 с:

import time
start_time = time.time()
num = 0
for x in range(0, 10000000):
    # num += 2 * (x * x)
    num += 2 * x * x
print("--- %s seconds ---" % (time.time() - start_time))

, если я заменю 2 * x * x на 2 *(x * x), это займет от 2.04 до 2.25.Как получилось?

С другой стороны, в Java все наоборот: 2 * (x * x) быстрее в Java.Ссылка на тест Java: Почему 2 * (i * i) быстрее, чем 2 * i * i в Java?

Я запускал каждую версию программы 10 раз, вот результаты.

   2 * x * x        |   2 * (x * x)
---------------------------------------
1.7717654705047607  | 2.0789272785186768
1.735931396484375   | 2.1166207790374756
1.7093875408172607  | 2.024367570877075
1.7004504203796387  | 2.047525405883789
1.6676218509674072  | 2.254328966140747
1.699510097503662   | 2.0949244499206543
1.6889283657073975  | 2.0841963291168213
1.7243537902832031  | 2.1290600299835205
1.712965488433838   | 2.1942825317382812
1.7622807025909424  | 2.1200053691864014

Ответы [ 4 ]

0 голосов
/ 01 декабря 2018

Из того, что я могу сказать, это сводится к немного большему доступу к памяти в версии с использованием 2 * (x * x).Я распечатал разобранный байт-код, и он, кажется, доказывает, что:

Соответствующая часть 2 * x * x:

7          28 LOAD_FAST                1 (num)
           30 LOAD_CONST               3 (2)
           32 LOAD_FAST                2 (x)
           34 BINARY_MULTIPLY
           36 LOAD_FAST                2 (x)
           38 BINARY_MULTIPLY
           40 INPLACE_ADD
           42 STORE_FAST               1 (num)
           44 JUMP_ABSOLUTE           24

Соответствующая часть 2 * (x * x):

  7          28 LOAD_FAST                1 (num)
             30 LOAD_CONST               3 (2)
             32 LOAD_FAST                2 (x)
             34 LOAD_FAST                2 (x)
             36 BINARY_MULTIPLY                 <=== 1st multiply x*x in a temp value
             38 BINARY_MULTIPLY                 <=== then multiply result with 2
             40 INPLACE_ADD
             42 STORE_FAST               1 (num)
             44 JUMP_ABSOLUTE           24
0 голосов
/ 01 декабря 2018

Интерфейс Python для представления целых чисел является специальным, он использует слоты по 30 битов:

In [6]: sys.getsizeof(2**30-1)
Out[6]: 28 # one slot + heading

In [7]: sys.getsizeof(2**30)
Out[7]: 32 # two slots 

Так что все происходит так, как будто Python считает в базе B = 2**30 = 1 073 741 824 ~1 billion.

Для человека, которыйхотите вычислить 2 * 4 * 4, двумя способами:

  • (2 * 4) * 4 = 8 * 4 = 32 = 30 + 2 немедленно, если вы знаете свои таблицы добавления.
  • 2 * (4 * 4) = 2 * 16 = 2 * 10 + 2 * 6 = (2 * 10 + 10) + 2 = 30 + 2, так как мы должны отложить операцию.

У Python такая же проблема.Если x является числом, таким как 2x < B < x², пусть x² = aB+b, с a,b <B. хранится в 2 слотах, которые я отмечаю (a|b).Вычисления приводят к (без управления переносами здесь):

   (x*x)*2 =>  (a|b)*2 => (2*a|2*b)
   (2*x)*x =>  (2x)*x =>(2a|2b)

в первом случае операция 2* выполняется два раза против только одного в первом случае.Это объясняет разницу.

0 голосов
/ 01 декабря 2018

Прежде всего, обратите внимание, что мы не видим то же самое в Python 2.x:

>>> timeit("for i in range(1000): 2*i*i")
51.00784397125244
>>> timeit("for i in range(1000): 2*(i*i)")
50.48330092430115

Так что это наводит нас на мысль, что это связано с тем, как изменились целые числа в Python 3:в частности, Python 3 везде использует long (произвольно большие целые числа).

Для достаточно маленьких целых чисел (включая те, которые мы здесь рассматриваем), CPython фактически просто использует O (MN) grade-алгоритм умножения цифр на цифру (для больших целых чисел он переключается на алгоритм Карацубы ).Вы можете сами увидеть это в источнике .

Количество цифр в x*x примерно в два раза больше, чем 2*x или x (так как log (x 2)) = 2 log (x)).Обратите внимание, что «цифра» в этом контексте - это не цифра от 10 до 30, а 30-битное значение (которые рассматриваются как однозначные в реализации CPython).Следовательно, 2 является однозначным значением, а x и 2*x являются однозначными значениями для всех итераций цикла, но x*x является двузначным для x >= 2**15.Следовательно, для x >= 2**15, 2*x*x требует только умножения на одну цифру, тогда как 2*(x*x) требует умножения на одну цифру и одну цифру (так как x*x имеет 2 30-битовые цифры).

Вот прямой способ увидеть это (Python 3):

>>> timeit("a*b", "a,b = 2, 123456**2", number=100000000)
5.796971936999967
>>> timeit("a*b", "a,b = 2*123456, 123456", number=100000000)
4.3559221399999615

Опять же, сравните это с Python 2, который везде не использует целые числа произвольной длины:

>>> timeit("a*b", "a,b = 2, 123456**2", number=100000000)
3.0912468433380127
>>> timeit("a*b", "a,b = 2*123456, 123456", number=100000000)
3.1120400428771973

(Одно интересное примечание: если вы посмотрите на источник, вы увидите, что алгоритм фактически имеет особый случай для возведения в квадрат чисел (что мы здесь делаем), но даже все же этонедостаточно, чтобы преодолеть тот факт, что 2*(x*x) просто требует обработки большего количества цифр.)

0 голосов
/ 01 декабря 2018

Если ваш тест верен (не проверен), это может быть связано с тем, что целые числа Python могут быть двумя разными вещами: нативные целые числа, когда они маленькие (с быстрым вычислением), и большие целые числа, когда они увеличиваются вразмер (медленное вычисление).Первый синтаксис сохраняет размер меньше после первой операции, в то время как второй синтаксис может привести к двум операциям с большими целыми числами.

...