Из этой страницы мы знаем, что:
Связанные сравнения выполняются быстрее, чем использование
and
оператора. Пишитеx < y < z
вместоx < y and y < z
.
Однако я получил другой результат, проверяя следующие фрагменты кода:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
Похоже, что x < y and y < z
быстрее чем x < y < z
. Зачем?
После поиска некоторых сообщений на этом сайте (например, этого ) я знаю, что «оценивается только один раз» - это ключ к успеху x < y < z
, однако я все еще не понимаю. Для дальнейшего изучения я разобрал эти две функции, используя dis.dis
:
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
И результат:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
Кажется, что у x < y and y < z
него меньше скрытых команд, чем у x < y < z
. Стоит ли считать x < y and y < z
быстрее, чем x < y < z
?
Протестировано с Python 2.7.6 на процессоре Intel (R) Xeon (R) E5640 @ 2,67 ГГц.
python
performance
zangw
источник
источник
timeit
тесты, я заинтересовался этим.y
это не просто поиск переменной, а более дорогой процесс, такой как вызов функции? Т.е.10 < max(range(100)) < 15
быстрее, чем10 < max(range(100)) and max(range(100)) < 15
потому чтоmax(range(100))
вызывается один раз для обоих сравнений.Ответы:
Разница в том, что in
x < y < z
y
оценивается только один раз. Это не имеет большого значения, если y - переменная, но имеет значение, когда это вызов функции, для вычисления которого требуется некоторое время.источник
sleep()
функция внутри?Оптимальный байт-код для обеих функций, которые вы определили, будет
потому что результат сравнения не используется. Сделаем ситуацию интереснее, вернув результат сравнения. Также давайте сделаем так, чтобы результат не был известен во время компиляции.
Опять же, две версии сравнения семантически идентичны, поэтому оптимальный байт-код одинаков для обеих конструкций. Насколько я могу понять, это будет выглядеть так. Я аннотировал каждую строку с содержимым стека до и после каждого кода операции в нотации Forth (верх стека справа,
--
делится до и после, завершение?
указывает на то, что могло или не могло быть там). Обратите внимание, чтоRETURN_VALUE
отбрасывает все, что осталось в стеке под возвращенным значением.Если реализация языка CPython, PyPy и т. Д. Не генерирует этот байт-код (или его собственную эквивалентную последовательность операций) для обоих вариантов, это демонстрирует низкое качество этого компилятора байт-кода . Получение из последовательностей байт-кода, которые вы разместили выше, является решенной проблемой (я думаю, все, что вам нужно в этом случае, - это сворачивание констант , удаление мертвого кода и лучшее моделирование содержимого стека; обычное устранение подвыражения также было бы дешевым и ценным ), и нет никакого оправдания тому, чтобы не делать этого в современной языковой реализации.
Теперь случается, что все текущие реализации языка имеют некачественные компиляторы байт-кода. Но вы должны игнорировать это при кодировании! Представьте, что компилятор байт-кода хорош, и напишите наиболее читаемый код. В любом случае, вероятно, будет достаточно быстро. Если это не так, сначала поищите алгоритмические улучшения, а затем попробуйте Cython - это обеспечит гораздо больше улучшений при тех же усилиях, чем любые настройки на уровне выражений, которые вы могли бы применить.
источник
interesting_compare
простой байт-код вверху (который будет работать только с встраиванием). Совершенно оффтоп, но: Встраивание - одна из самых важных оптимизаций для любого компилятора. Вы можете попробовать запустить некоторые тесты, скажем, с HotSpot на реальных программах (а не с некоторыми математическими тестами, которые проводят 99% своего времени в одном цикле горячего цикла, оптимизированном вручную [и, следовательно, в любом случае не имеет больше вызовов функций]), когда вы отключите его возможность встраивать что угодно - вы увидите большие регрессии.interesting_compare
.y
не изменяют стек, поскольку в нем много инструментов отладки.Поскольку разница в выводе, похоже, связана с отсутствием оптимизации, я думаю, вам следует игнорировать эту разницу в большинстве случаев - может быть, разница исчезнет. Разница в том, что
y
оценивать нужно только один раз, и это решается путем дублирования его в стеке, что требует дополнительныхPOP_TOP
-LOAD_FAST
хотя решение для использования может быть возможным.Однако важное различие заключается в том, что во
x<y and y<z
второмy
случае следует оценивать дважды, если онx<y
оценивается как истинный, это имеет значение, если оценкаy
занимает значительное время или имеет побочные эффекты.В большинстве случаев вам следует использовать,
x<y<z
хотя он несколько медленнее.источник
Прежде всего, ваше сравнение практически бессмысленно, потому что две разные конструкции не были введены для повышения производительности, поэтому вам не следует решать, использовать ли одну вместо другой на основе этого.
x < y < z
Конструкция:x
,y
иz
один раз проверить, выполняется ли все условие. Использованиеand
изменяет семантику путемy
многократной оценки , что может изменить результат .Поэтому выбирайте одно вместо другого в зависимости от желаемой семантики и, если они эквивалентны, будет ли один более читаемым, чем другой.
Это говорит о том, что более дизассемблированный код не означает более медленный код. Однако выполнение большего количества операций с байт-кодом означает, что каждая операция проще и все же требует итерации основного цикла. Это означает, что если операции, которые вы выполняете, очень быстрые (например, поиск локальной переменной, как вы там делаете), то накладные расходы на выполнение большего количества операций с байт-кодом могут иметь значение.
Но обратите внимание, что этот результат не сохраняется в более общей ситуации, а только в «худшем случае», который вы можете профилировать. Как отмечали другие, если вы измените
y
что-то, что займет даже немного больше времени, вы увидите, что результаты меняются, потому что цепная нотация оценивает это только один раз.Подводя итог:
источник