Эта статья была рецензирована Юнесом Рафи . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
Вдохновленная недавней статьей о том, как выполняется код Ruby , эта статья описывает процесс выполнения кода PHP.
Вступление
Когда мы выполняем часть кода PHP, многое происходит под капотом. Вообще говоря, интерпретатор PHP проходит четыре этапа при выполнении кода:
- лексический
- анализ
- компиляция
- интерпретация
Эта статья пролистает эти этапы и покажет, как мы можем просмотреть результаты каждого этапа, чтобы реально увидеть, что происходит. Обратите внимание, что хотя некоторые из используемых расширений уже должны быть частью вашей установки PHP (например, tokenizer и OPcache), другие необходимо будет установить и включить вручную (например, php-ast и VLD).
Этап 1 — Лексинг
Лексирование (или токенизация) — это процесс превращения строки (в данном случае исходного кода PHP) в последовательность токенов. Токен — это просто именованный идентификатор значения, которому он соответствует. PHP использует re2c для генерации своего лексера из файла определения zend_language_scanner.l .
Мы можем видеть вывод этапа лексирования через расширение токенизатора :
$code = <<<'code'
<?php
$a = 1;
code;
$tokens = token_get_all($code);
foreach ($tokens as $token) {
if (is_array($token)) {
echo "Line {$token[2]}: ", token_name($token[0]), " ('{$token[1]}')", PHP_EOL;
} else {
var_dump($token);
}
}
Выходы:
Line 1: T_OPEN_TAG ('<?php
')
Line 2: T_VARIABLE ('$a')
Line 2: T_WHITESPACE (' ')
string(1) "="
Line 2: T_WHITESPACE (' ')
Line 2: T_LNUMBER ('1')
string(1) ";"
Из вышеприведенного вывода есть пара примечательных моментов. Во-первых, не все фрагменты исходного кода называются токенами. Вместо этого некоторые символы считаются токенами сами по себе (например, =
;
:
?
Второй момент заключается в том, что на самом деле лексер делает немного больше, чем просто выводит поток токенов. Кроме того, в большинстве случаев хранятся лексема (значение, сопоставленное токену) и номер строки сопоставленного токена (который используется для таких вещей, как трассировка стека).
Этап 2 — Разбор
Парсер также генерируется, на этот раз с Bison через файл грамматики BNF . В PHP используется контекстно-свободная грамматика LALR (1) (смотреть вперед, слева направо). Часть просмотра вперед просто означает, что синтаксический анализатор может просмотреть n
Слева направо означает, что он анализирует поток токенов слева направо.
Сгенерированный этап синтаксического анализа принимает поток токенов от лексера в качестве входных данных и имеет два задания. Во-первых, он проверяет правильность порядка токенов, пытаясь сопоставить их с любым из правил грамматики, определенных в его файле грамматики BNF. Это гарантирует, что допустимые языковые конструкции формируются токенами в потоке токенов. Вторая задача синтаксического анализатора — создать абстрактное синтаксическое дерево (AST) — древовидное представление исходного кода, которое будет использоваться на следующем этапе (компиляция).
Мы можем просмотреть форму AST, созданную анализатором, используя расширение php-ast . Внутренний AST не предоставляется напрямую, потому что он не особенно «чист» для работы (с точки зрения согласованности и общего удобства использования), и поэтому расширение php-ast выполняет несколько преобразований, чтобы сделать его более приятным для работы.
Давайте посмотрим на AST для элементарного кода:
$code = <<<'code'
<?php
$a = 1;
code;
print_r(ast\parse_code($code, 30));
Выход:
ast\Node Object (
[kind] => 132
[flags] => 0
[lineno] => 1
[children] => Array (
[0] => ast\Node Object (
[kind] => 517
[flags] => 0
[lineno] => 2
[children] => Array (
[var] => ast\Node Object (
[kind] => 256
[flags] => 0
[lineno] => 2
[children] => Array (
[name] => a
)
)
[expr] => 1
)
)
)
)
Узлы дерева (которые обычно имеют тип ast\Node
-
kind
у каждого есть соответствующая константа (например,AST_STMT_LIST
AST_ASSIGN
AST_VAR
-
flags
ast\AST_BINARY_OP
-
lineno
-
children
Выход AST на этом этапе удобен для таких инструментов, как статические анализаторы кода (например, Phan ).
Этап 3 — Компиляция
Этап компиляции использует AST, где он генерирует коды операций путем рекурсивного обхода дерева. Этот этап также выполняет несколько оптимизаций. К ним относятся разрешение некоторых вызовов функций с буквальными аргументами (например, strlen("abc")
int(3)
60 * 60 * 24
int(86400)
На этом этапе мы можем проверить вывод кода операции несколькими способами, в том числе с помощью OPcache , VLD и PHPDBG . Я собираюсь использовать VLD для этого, так как я чувствую, что результат будет более дружелюбным.
Посмотрим, что получится для следующего скрипта file.php :
if (PHP_VERSION === '7.1.0-dev') {
echo 'Yay', PHP_EOL;
}
Выполнение следующей команды:
php -dopcache.enable_cli=1 -dopcache.optimization_level=0 -dvld.active=1 -dvld.execute=0 file.php
Наш вывод:
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
3 0 E > > JMPZ <true>, ->3
4 1 > ECHO 'Yay'
2 ECHO '%0A'
7 3 > > RETURN 1
Эти коды операций напоминают исходный код, достаточный для выполнения основных операций. (Я не буду вдаваться в подробности кодов операций в этой статье, так как это займет несколько целых статей само по себе.) На уровне кода операции в приведенном выше сценарии не было применено никаких оптимизаций — но, как мы можем видеть, фаза компиляции сделал некоторые, разрешив постоянное условие ( PHP_VERSION === '7.1.0-dev'
true
OPcache делает больше, чем просто кэширует коды операций (таким образом, минуя этапы лексирования, синтаксического анализа и компиляции). Он также включает в себя множество различных уровней оптимизации. Давайте повысим уровень оптимизации до четырех проходов, чтобы посмотреть, что получится:
Команда:
php -dopcache.enable_cli=1 -dopcache.optimization_level=1111 -dvld.active=-1 -dvld.execute=0 file.php
Выход:
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
4 0 E > ECHO 'Yay%0A'
7 1 > RETURN 1
Мы можем видеть, что константное условие было удалено, и две инструкции ECHO
Это всего лишь пример многих оптимизаций, которые OPcache применяет при выполнении операций над кодами операций сценария. Я не буду проходить различные уровни оптимизации в этой статье, поскольку это также будет статья сама по себе.
Этап 4 — Интерпретация
Заключительный этап — интерпретация кодов операций. Здесь операционные коды запускаются на виртуальной машине Zend Engine (ZE). На самом деле очень мало что можно сказать об этом этапе (по крайней мере, с точки зрения высокого уровня). Вывод в значительной степени независимо от того, что ваш PHP-скрипт выводит с помощью таких команд, как echo
print
var_dump
Таким образом, вместо того, чтобы углубляться во что-то сложное на этом этапе, вот забавный факт: PHP требует себя в качестве зависимости при создании собственной виртуальной машины. Это связано с тем, что виртуальная машина генерируется сценарием PHP, поскольку ее проще писать и поддерживать.
Вывод
Мы кратко рассмотрели четыре этапа, которые проходит интерпретатор PHP при запуске кода PHP. Это включает использование различных расширений (включая tokenizer, php-ast, OPcache и VLD) для манипулирования и просмотра выходных данных каждого этапа.
Я надеюсь, что эта статья помогла вам лучше понять интерпретатор PHP, а также продемонстрировала важность расширения OPcache (как для его возможностей кэширования, так и для оптимизации).