016 Пользовательские оптимизаторы
В большинстве распространенных функций в Zephir используются внутренние оптимизаторы. Оптимизатор работает как перехватчик для вызовов функций. Оптимизатор заменяет вызов функции в пользовательском пространстве PHP прямыми Си-вызовами, которые выполняются быстрее и имеют более низкие накладные расходы, повышающие производительность.
Чтобы создать оптимизатор, вам нужно создать класс в каталоге «optimizers», необходимо использовать следующее соглашение (вы можете настроить название этого каталога в файле config.json
; см. ниже). Следующие соглашения по именованию являются обязательными:
Функция в Zephir | Название класса оптимизатора | Путь оптимизатора | Функция в C |
---|---|---|---|
calculate_pi | CalculatePiOptimizer | optimizers/CalculatePiOptimizer.php | my_calculate_pi |
Обратите внимание, что оптимизатор написан на PHP, а не на Zephir. Он используется во время компиляции, чтобы программно генерировать соответствующий код C для вашего расширения для вызова. Это оправдано для проверки того, что Аргументы и возвращаемые типы соответствуют тому, что функция C фактически требует, предотвращая zephir от генерации недопустимого кода C.
Это основная структура для ‘optimizer’:
<?php namespace Zephir\Optimizers\FunctionCall; use Zephir\Call; use Zephir\CompilationContext; use Zephir\Compiler\CompilerException; use Zephir\Optimizers\OptimizerAbstract; class CalculatePiOptimizer extends OptimizerAbstract { public function optimize(array $expression, Call $call, CompilationContext $context) { //... } }
Реализация оптимизаторов в большой степени зависит от типа кода, который вы хотите сгенерировать. В нашем примере мы собираемся заменить вызов этой функции вызовом c-функции. В Zephir код, используемый для вызова этой функции:
let pi = calculate_pi(1000);
Таким образом, оптимизатор будет ожидать только один параметр, мы должны подтвердить это, чтобы избежать проблем позже:
<?php public function optimize(array $expression, Call $call, CompilationContext $context) { if (!isset($expression['parameters'])) { throw new CompilerException("'calculate_pi' требуется один параметр"); } if (count($expression['parameters']) < 2) { throw new CompilerException("'calculate_pi' требуется один параметр"); } //... }
Есть только что вызванные функции и они не возвращают никакого значения, наша функция возвращает значение, которое является вычисленным значением PI. Поэтому мы должны знать, что тип переменной, используемой для получения этого вычисленного значения, - ОК:
<?php public function optimize(array $expression, Call $call, CompilationContext $context) { if (!isset($expression['parameters'])) { throw new CompilerException("'calculate_pi' требуется один параметр"); } if (count($expression['parameters']) < 2) { throw new CompilerException("'calculate_pi' требуется один параметр"); } /** * Обработка возвращаемого символа */ $call->processExpectedReturn($context); $symbolVariable = $call->getSymbolVariable(); if ($symbolVariable->isNotDouble()) { throw new CompilerException("Рассчитанные значения PI могут быть сохранены только в двойных переменных", $expression); } //... }
Мы проверяем, будет ли возвращаемое значение храниться в типе переменной ‘double’, если не выбрано исключение компилятора.
Следующее, что нам нужно сделать, это обработать параметры, переданные функции:
<?php $resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression);
Как хорошая практика в Zephir важна для создания функций, которые не изменяют их параметры, если вы изменяете переданные параметры, Zephir нужно будет распределить память для переданных констант, и вам придется использовать getResolvedParams вместо getReadOnlyResolvedParams.
Код, возвращаемый этими методами, является допустимым C-кодом, который может использоваться в принтере кода для генерации вызова c-функции:
<?php //Generate the C-code return new CompiledExpression('double', 'calculate_pi( ' . $resolvedParams[0] . ')', $expression);
Все оптимизаторы должны возвращать экземпляр CompiledExpression, это сообщит компилятору тип, возвращаемый кодом и связанным с ним C-кодом.
Полный код оптимизатора:
<?php class CalculatePiOptimizer extends OptimizerAbstract { public function optimize(array $expression, Call $call, CompilationContext $context) { if (!isset($expression['parameters'])) { throw new CompilerException("'calculate_pi' требуется один параметр"); } if (count($expression['parameters']) < 2) { throw new CompilerException("'calculate_pi' требуется один параметр"); } /** * Обработка возвращаемого символа */ $call->processExpectedReturn($context); $symbolVariable = $call->getSymbolVariable(); if ($symbolVariable->isNotDouble()) { throw new CompilerException("Рассчитанные значения PI могут быть сохранены только в двойных переменных", $expression); } $resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression); return new CompiledExpression('double', 'my_calculate_pi( ' . $resolvedParams[0] . ')', $expression); } }
Код, реализующий функцию "my_calculate_pi", написан на языке C и должен компилироваться вместе с расширением.
Этот код должен быть помещен в каталог ext/, где бы вы ни находились; просто убедитесь, что эти файлы не конфликтуют с файлами, сгенерированными Zephir.
Этот файл должен содержать заголовки Zend Engine и реализацию функции на языке C:
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ext.h" double my_calculate_pi(zval *accuracy) { return 0.0; }
Этот файл необходимо добавить в специальный раздел файла config.json :
"extra-sources": [ "utils/pi.c" ]
Наконец, вам нужно будет указать, где Zephir может найти ваш оптимизатор, используя опцию конфигурации optimizer-dirs.
"optimizer-dirs": [ "optimizers" ]
Проверьте полный исходный код этого примера здесь.