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"
]
Проверьте полный исходный код этого примера здесь.