Есть ли разница между x += y и x = x + y для встроенных типов? Если не в семантике, то в быстродействии?
Ответ NB Это всё проверялось для CPython 3.10. В других интерпретаторах/компиляторах результат может быть другим (другим по времени, но тем же по результату!). Разница в одной инструкции: INPLACE_ADD или BINARY_ADD. def f(): def g(): x = 10 x = 10 x += 1 x = x + 1 0 LOAD_CONST 1 (10) 0 LOAD_CONST 1 (10) 2 STORE_FAST 0 (x) 2 STORE_FAST 0 (x) 4 LOAD_FAST 0 (x) 4 LOAD_FAST 0 (x) 6 LOAD_CONST 2 (1) 6 LOAD_CONST 2 (1) 8 INPLACE_ADD 8 BINARY_ADD 10 STORE_FAST 0 (x) 10 STORE_FAST 0 (x) 12 LOAD_CONST 0 (None) 12 LOAD_CONST 0 (None) 14 RETURN_VALUE 14 RETURN_VALUE
Числа, строки и кортежи Для целых и вещественных чисел слот nb_inplace_add не заполнен (например для целых). Это значит что обе инструкции INPLACE_ADD и BINARY_ADD выполняют один и тот же код из слота nb_add. Результат работы и время одинаковы и в теории и на практике. Для строк и кортежей ситуация такая же. Инструкции исполняют один и тот же код - результат одинаковый и время одинаковое. Списки Тут хитрее: отличается и результат и время. += выигрывает, так как не изготавливает новый список - конкатенацию аргументов, а сразу добавляет элементы правого списка в левый. Примеры ниже немного нечестные по отношению к += - список n растёт. Тем не менее += лидирует с огромным отрывом. NB Разная семантика! f и g по разному работают со списком n. f его меняет, g оставляет неизменным. В случае списков последняя инструкция STORE_FAST не нужна - это фактически самоприсваивание. Но код не знает про типы и делает лишнюю операцию. Мелочь, показывающая почему компиляторы любят строгую типизацию. Очень быстро: n = [0] * 1000 m = [1] def f(): x = n x += m for _ in range(1_000_000): f() $ time python f.py real 0m0.202s user 0m0.200s sys 0m0.000s
Очень медленно: n = [0] * 1000 m = [1] def g(): x = n x = x + m for _ in range(1_000_000): g() $ time python g.py real 0m4.403s user 0m4.296s sys 0m0.004s