[PHP] Порядок вычисления в PHP (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Примечание переводчика.
Никита Попов внёс и продолжает вносить огромный вклад в развитие языка PHP. Он очень хорошо понимает внутренности движка PHP и в данной статье он объясняет некоторые особенности работы PHP в плане порядка вычисления выражений, которые, пожалуй, особо нигде и не найти. Этой статье около 7 лет и она практически не потеряла актуальность, однако найти её довольно сложно, потому что её нет в блоге Никиты Попова, а она опубликована в его gist-ах на гитхабе. Думаю полезно будет представить её сообществу на русском языке.
В своём любимом сообществе lolphp на реддит я наткнулся на пост, где люди удивляются результату следующего кода:
<?php
$a = 1;
$c = $a + $a++;
var_dump($c); // int(3)
$a = 1;
$c = $a + $a + $a++;
var_dump($c); // int(3)
Как вы видите, выражения ($a + $a++) и ($a + $a + $a++) дают одинаковый результат, что довольно неожиданно. Что же здесь происходит?
Приоритет и ассоциативность операторов
Многие люди думают, что порядок вычисления выражения определяется приоритетом и ассоциативностью операторов, но это не так. Приоритет и ассоциативность определяют лишь порядок группировки операций в выражении.
В первом выражении $c = $a + $a++; пост-инкремент "++" имеет более высокий приоритет, чем "+", поэтому "$a++" является отдельной группой:
$c = $a + ($a++);
Во втором выражении $c = $a + $a + $a++; пост-инкремент "++" снова имеет более высокий приоритет, нежели "+":
$c = $a + $a + ($a++);
И "+" является лево-ассоциативным оператором, поэтому левый "+" формирует отдельную группу:
$c = ($a + $a) + ($a++);
Примечание переводчика: код из оригинальной статьи представлял примеры выражений и не являлся валидным, поэтому он был несколько скорректирован.
Что же это говорит нам о порядке вычисления? Ничего. Приоритет и ассоциативность операторов определяют группировку, но они не говорят о том, в каком порядке эти группы будут выполняться. В частности, в последнем примере, как ($a + $a), так и ($a++) может быть выполнено в первую очередь.
В PHP на самом деле не определено что же произойдет. Одна версия PHP может выдать вам один результат, а другая — другой. Не пишите код, который зависит от какого-то определенного порядка вычисления выражения.
CV оптимизации
Все-таки, даже не смотря на то, что PHP не определяет порядок вычисления, будет интересно выяснить, почему же мы получаем довольно неожиданный результат в первом выражении (он будет одинаковым во всех последних версиях PHP).
Причина такого результата кроется в оптимизации компилируемых переменных (compiled variables, CV), которая была добавлена в PHP 5.1. Данная оптимизация по сути позволяет простым переменным (например, $a, но не $a->b или $a['b']) напрямую быть операндами опкодов. Опкоды — это то, что PHP генерирует из вашего скрипта и то, что выполняет Zend VM (виртуальная машина Zend). Каждый опкод имеет максимум 2 операнда и опциональный результат.
А теперь давайте посмотрим на опкоды, сгенерированные двумя примерами кода.
Начнем с $a + $a + $a++:
// code:
$a = 1;
$c = ($a + $a) + ($a++);
// opcodes:
ASSIGN $a, 1
$tmp_1 = ADD $a, $a
$tmp_2 = POST_INC $a
$tmp_3 = ADD $tmp_1, $tmp_2
ASSIGN $c, $tmp_3
Сгенерированные опкоды получаются довольно понятными:
- сначала идет присваивание $a = 1,
- далее — сложение $a + $a с сохранением результата во временную переменную $tmp_1,
- затем выполняется пост-инкремент $a с сохранением результата в $tmp_2,
- и, наконец, сложение обоих временных переменных и присвоение результата переменной $c.
Вычисление здесь происходит слева направо (сначала выполняется $a + $a, затем $a++), как вы, наверное, и ожидали.
А теперь давайте рассмотрим вариант $a + $a++:
// code:
$a = 1;
$c = $a + ($a++);
// opcodes:
ASSIGN $a, 1
$tmp_1 = POST_INC $a
$tmp_2 = ADD $a, $tmp_1
ASSIGN $c, $tmp_2
Как вы видите, в этом случае POST_INC ($a++) выполянется в первую очередь, и значение $a считывается напрямую опкодом ADD. Почему? Потому что чтение значения переменной не требует дополнительных опкодов. Любой опкод умеет считывать значения простых переменных. Это и есть CV оптимизация.
Когда не происходит CV оптимизация
Примечание переводчика: в оригинальной статье Никита Попов рассматривает вариант, когда CV оптимизации не срабатывают из-за использования оператора подавления ошибок @. Но это происходит лишь в PHP 5.x, а в PHP 7 оптимизации работают в том числе и в этом случае. Поэтому перевод оригинала, применимый только к PHP 5 убран в спойлер, а мы же рассмотрим вариант, когда код из примеров работает по-другому из-за отсутствия CV оптимизаций в силу не использования CV.
К чему приводит использование оператора подавления ошибок в PHP 5.x
SPL
Существуют некоторые (редкие) случаи, когда CV оптимизация не выполняется, например, в случае использования оператора подавления ошибок @.
Давайте проверим. Мы снова возьмем выражение $a + $a++, но в этот раз погасим ошибки, используя оператор @:
<?php
$a = 1;
@ $c = $a + $a++;
var_dump($c); // int(2)
Теперь, когда мы применили оператор подавления ошибок, результат неожиданно поменялся с 3 на 2. И чтобы выяснить почему, давайте взглянем на опкоды:
ASSIGN $a, 1
$tmp_1 = BEGIN_SILENCE
$var_3 = FETCH_R 'a'
$tmp_4 = POST_INC $a
$tmp_5 = ADD $var_3, $tmp_4
$var_2 = FETCH_W 'c'
ASSIGN $var_2, $tmp_5
END_SILENCE $tmp_1
Как мы видим, несколько вещей здесь изменились. Во-первых, весь код сейчас обрамлён опкодами BEGIN_SILENCE и END_SILENCE для обработки подавления ошибок. Они на самом деле нам не сильно интересны. Во-вторых, обращение к переменным $a и $b сейчас происходит при помощи FETCH_R (для считывания) и FETCH_W (для записи) вместо прямого использования их в качестве операндов.
Именно потому, что обращение к $a сейчас имеет отдельный опкод, оно будет происходить до инкремента и результат будет другим.
CV оптимизация не выполняется, например, в случае обращения к элементам массивов или свойствам объектов.
Давайте проверим. Мы снова возьмем выражение $a + $a++, но в этот раз будем использовать обращение к элементу массива вместо переменной:
<?php
$a = [1];
$c = $a[0] + $a[0]++;
var_dump($c); // int(2)
Теперь, когда мы начали использовать массив, результат неожиданно поменялся с 3 на 2. И чтобы выяснить почему, давайте взглянем на опкоды:
ASSIGN $a, [1]
$tmp_3 = FETCH_DIM_R 'a', 0
$var_4 = FETCH_DIM_RW 'a', 0
$tmp_5 = POST_INC $var_4
$tmp_6 = ADD $tmp_3, $tmp_5
ASSIGN $c, $tmp_6
Как мы видим, обращение к элементу массива здесь происходит при помощи FETCH_DIM_R (для считывания) и FETCH_DIM_RW (для чтения/записи) вместо прямого использования его в качестве операндов.
Именно потому, что получение операндов для сложения сейчас имеет отдельный опкод, оно будет происходить до инкремента, и результат будет другим.
На самостоятельное изучение оставлю аналогичный пример, но с использованием объектов. Найти пример кода можно по этой ссылке на 3v4l.org.
Выводы
Если делать какие-то выводы из всего этого, то я думаю они должны быть такими:
- Не нужно полагаться на порядок вычисления выражений. Он не определен.
- Оператор @ отключает CV оптимизации и в результате ухудшает производительность. Оператор @ в принципе плохо сказывается на производительности.
~nikic
Примечание переводчика: как было сказано выше, @ отключает CV оптимизации только в 5.x, в PHP 7 CV оптимизации имеют место даже в случае использования оператора подавления ошибок (но возможно, это происходит не во всех случаях). У Никиты Попова в блоге есть интересная статья Static Optimization in PHP 7, на случай, если кто-то хочет глубже копнуть тему оптимизации.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Nikita Popov
===========Похожие новости:
- [PHP] PHP Internals News Эпизод #38: предзагрузка и WeakMaps (перевод)
- [PHP, Системное администрирование] Apache & Nginx. Связаны одной цепью (2 часть)
- [PHP] Hire PHP Developers: Cost & Procedure
- [PHP] Получение видео из Tik Tok без водяного знака
- [PHP, ООП, Совершенный код] Вы уверены, что пишете объектно-ориентированный код? (перевод)
- [PHP] Как я писал кодогенератор на PHP и что из этого получилось
- [JavaScript, PHP, Ненормальное программирование, Программирование, Разработка веб-сайтов] Inertia.js – современный монолит
- [Node.JS, PHP, Perl, Python, Информационная безопасность] Трюки с переменными среды (перевод)
- [PHP, Алгоритмы, Информационная безопасность, Криптография] Разработка собственного алгоритма симметричного шифрования на Php
- [PHP] Мне не нравится то, во что превращается PHP
Теги для поиска: #_php, #_php, #_php_internals, #_order_of_evaluation, #_porjadok_vypolnenija (порядок выполнения), #_porjadok_vychislenija (порядок вычисления), #_php
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:22
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Примечание переводчика. Никита Попов внёс и продолжает вносить огромный вклад в развитие языка PHP. Он очень хорошо понимает внутренности движка PHP и в данной статье он объясняет некоторые особенности работы PHP в плане порядка вычисления выражений, которые, пожалуй, особо нигде и не найти. Этой статье около 7 лет и она практически не потеряла актуальность, однако найти её довольно сложно, потому что её нет в блоге Никиты Попова, а она опубликована в его gist-ах на гитхабе. Думаю полезно будет представить её сообществу на русском языке. В своём любимом сообществе lolphp на реддит я наткнулся на пост, где люди удивляются результату следующего кода: <?php
$a = 1; $c = $a + $a++; var_dump($c); // int(3) $a = 1; $c = $a + $a + $a++; var_dump($c); // int(3) Как вы видите, выражения ($a + $a++) и ($a + $a + $a++) дают одинаковый результат, что довольно неожиданно. Что же здесь происходит? Приоритет и ассоциативность операторов Многие люди думают, что порядок вычисления выражения определяется приоритетом и ассоциативностью операторов, но это не так. Приоритет и ассоциативность определяют лишь порядок группировки операций в выражении. В первом выражении $c = $a + $a++; пост-инкремент "++" имеет более высокий приоритет, чем "+", поэтому "$a++" является отдельной группой: $c = $a + ($a++);
Во втором выражении $c = $a + $a + $a++; пост-инкремент "++" снова имеет более высокий приоритет, нежели "+": $c = $a + $a + ($a++);
И "+" является лево-ассоциативным оператором, поэтому левый "+" формирует отдельную группу: $c = ($a + $a) + ($a++);
Примечание переводчика: код из оригинальной статьи представлял примеры выражений и не являлся валидным, поэтому он был несколько скорректирован. Что же это говорит нам о порядке вычисления? Ничего. Приоритет и ассоциативность операторов определяют группировку, но они не говорят о том, в каком порядке эти группы будут выполняться. В частности, в последнем примере, как ($a + $a), так и ($a++) может быть выполнено в первую очередь. В PHP на самом деле не определено что же произойдет. Одна версия PHP может выдать вам один результат, а другая — другой. Не пишите код, который зависит от какого-то определенного порядка вычисления выражения. CV оптимизации Все-таки, даже не смотря на то, что PHP не определяет порядок вычисления, будет интересно выяснить, почему же мы получаем довольно неожиданный результат в первом выражении (он будет одинаковым во всех последних версиях PHP). Причина такого результата кроется в оптимизации компилируемых переменных (compiled variables, CV), которая была добавлена в PHP 5.1. Данная оптимизация по сути позволяет простым переменным (например, $a, но не $a->b или $a['b']) напрямую быть операндами опкодов. Опкоды — это то, что PHP генерирует из вашего скрипта и то, что выполняет Zend VM (виртуальная машина Zend). Каждый опкод имеет максимум 2 операнда и опциональный результат. А теперь давайте посмотрим на опкоды, сгенерированные двумя примерами кода. Начнем с $a + $a + $a++: // code:
$a = 1; $c = ($a + $a) + ($a++); // opcodes:
ASSIGN $a, 1 $tmp_1 = ADD $a, $a $tmp_2 = POST_INC $a $tmp_3 = ADD $tmp_1, $tmp_2 ASSIGN $c, $tmp_3 Сгенерированные опкоды получаются довольно понятными:
Вычисление здесь происходит слева направо (сначала выполняется $a + $a, затем $a++), как вы, наверное, и ожидали. А теперь давайте рассмотрим вариант $a + $a++: // code:
$a = 1; $c = $a + ($a++); // opcodes:
ASSIGN $a, 1 $tmp_1 = POST_INC $a $tmp_2 = ADD $a, $tmp_1 ASSIGN $c, $tmp_2 Как вы видите, в этом случае POST_INC ($a++) выполянется в первую очередь, и значение $a считывается напрямую опкодом ADD. Почему? Потому что чтение значения переменной не требует дополнительных опкодов. Любой опкод умеет считывать значения простых переменных. Это и есть CV оптимизация. Когда не происходит CV оптимизация Примечание переводчика: в оригинальной статье Никита Попов рассматривает вариант, когда CV оптимизации не срабатывают из-за использования оператора подавления ошибок @. Но это происходит лишь в PHP 5.x, а в PHP 7 оптимизации работают в том числе и в этом случае. Поэтому перевод оригинала, применимый только к PHP 5 убран в спойлер, а мы же рассмотрим вариант, когда код из примеров работает по-другому из-за отсутствия CV оптимизаций в силу не использования CV. К чему приводит использование оператора подавления ошибок в PHP 5.xSPLСуществуют некоторые (редкие) случаи, когда CV оптимизация не выполняется, например, в случае использования оператора подавления ошибок @.
Давайте проверим. Мы снова возьмем выражение $a + $a++, но в этот раз погасим ошибки, используя оператор @: <?php
$a = 1; @ $c = $a + $a++; var_dump($c); // int(2) Теперь, когда мы применили оператор подавления ошибок, результат неожиданно поменялся с 3 на 2. И чтобы выяснить почему, давайте взглянем на опкоды: ASSIGN $a, 1
$tmp_1 = BEGIN_SILENCE $var_3 = FETCH_R 'a' $tmp_4 = POST_INC $a $tmp_5 = ADD $var_3, $tmp_4 $var_2 = FETCH_W 'c' ASSIGN $var_2, $tmp_5 END_SILENCE $tmp_1 Как мы видим, несколько вещей здесь изменились. Во-первых, весь код сейчас обрамлён опкодами BEGIN_SILENCE и END_SILENCE для обработки подавления ошибок. Они на самом деле нам не сильно интересны. Во-вторых, обращение к переменным $a и $b сейчас происходит при помощи FETCH_R (для считывания) и FETCH_W (для записи) вместо прямого использования их в качестве операндов. Именно потому, что обращение к $a сейчас имеет отдельный опкод, оно будет происходить до инкремента и результат будет другим. CV оптимизация не выполняется, например, в случае обращения к элементам массивов или свойствам объектов. Давайте проверим. Мы снова возьмем выражение $a + $a++, но в этот раз будем использовать обращение к элементу массива вместо переменной: <?php
$a = [1]; $c = $a[0] + $a[0]++; var_dump($c); // int(2) Теперь, когда мы начали использовать массив, результат неожиданно поменялся с 3 на 2. И чтобы выяснить почему, давайте взглянем на опкоды: ASSIGN $a, [1]
$tmp_3 = FETCH_DIM_R 'a', 0 $var_4 = FETCH_DIM_RW 'a', 0 $tmp_5 = POST_INC $var_4 $tmp_6 = ADD $tmp_3, $tmp_5 ASSIGN $c, $tmp_6 Как мы видим, обращение к элементу массива здесь происходит при помощи FETCH_DIM_R (для считывания) и FETCH_DIM_RW (для чтения/записи) вместо прямого использования его в качестве операндов. Именно потому, что получение операндов для сложения сейчас имеет отдельный опкод, оно будет происходить до инкремента, и результат будет другим. На самостоятельное изучение оставлю аналогичный пример, но с использованием объектов. Найти пример кода можно по этой ссылке на 3v4l.org. Выводы Если делать какие-то выводы из всего этого, то я думаю они должны быть такими:
~nikic Примечание переводчика: как было сказано выше, @ отключает CV оптимизации только в 5.x, в PHP 7 CV оптимизации имеют место даже в случае использования оператора подавления ошибок (но возможно, это происходит не во всех случаях). У Никиты Попова в блоге есть интересная статья Static Optimization in PHP 7, на случай, если кто-то хочет глубже копнуть тему оптимизации. =========== Источник: habr.com =========== =========== Автор оригинала: Nikita Popov ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:22
Часовой пояс: UTC + 5