自定义优化器

Zephir 中最常见的函数使用内部优化器。 “优化器” 的工作方式类似于函数调用的拦截器。 一个“优化器”取代了对PHP代码块中通常定义的函数调用的直接C调用,后者更快,开销更低,从而提高了性能。

要创建优化器,您必须在“优化器”目录中创建一个类(您可以在config.json中配置该目录的名称; 见下文)。 必须使用以下命名约定:

在 Zephir的作用 优化器类名 优化器路径 C 中的函数
calculate_pi CalculatePiOptimizer optimizers/CalculatePiOptimizer.php my_calculate_pi

请注意, 优化器是用 PHP 编写的, 而不是 Zephir编写的。 在编译过程中, 它用于以编程方式为您的扩展调用生成适当的 c 代码。 它负责检查参数和返回类型是否与 c 函数实际需要的内容相匹配, 从而防止 Zephir 生成无效的 c 代码。

这是 “优化器” 的基本结构:

<?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' requires one parameter", $expression);
    }

    if (count($expression['parameters']) > 1) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    //...
}

有一些函数只是调用, 不返回任何值。 我们的函数返回一个值, 该值是计算出的 pi 值。 因此, 我们需要检查用于接收此计算值的变量的类型是否为 “确定”:

<?php

public function optimize(array $expression, Call $call, CompilationContext $context)
{

    if (!isset($expression['parameters'])) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    if (count($expression['parameters']) > 1) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    /**
     * Process the expected symbol to be returned
     */
    $call->processExpectedReturn($context);

    $symbolVariable = $call->getSymbolVariable();
    if (!$symbolVariable->isDouble()) {
        throw new CompilerException("Calculated PI values only can be stored in double variables", $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);

所有优化器都必须返回一个编译表达式实例。 这将告诉编译器代码返回的类型及其相关的c代码。

完整的优化器代码是:

<?php

namespace Zephir\Optimizers\FunctionCall;

use Zephir\Call;
use Zephir\CompilationContext;
use Zephir\CompiledExpression;
use Zephir\Compiler\CompilerException;
use Zephir\Optimizers\OptimizerAbstract;

class CalculatePiOptimizer extends OptimizerAbstract
{
    public function optimize(array $expression, Call $call, CompilationContext $context)
    {
        if (!isset($expression['parameters'])) {
            throw new CompilerException("'calculate_pi' requires one parameter", $expression);
        }

        if (count($expression['parameters']) > 1) {
            throw new CompilerException("'calculate_pi' requires one parameter", $expression);
        }

        /**
         * Process the expected symbol to be returned
         */
        $call->processExpectedReturn($context);

        $symbolVariable = $call->getSymbolVariable();
        if (!$symbolVariable->isDouble()) {
            throw new CompilerException("Calculated PI values only can be stored in double variables", $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;
}

This file must be added at a special section in the config.json file:

"extra-sources": [
    "utils/pi.c"
]

最后, 您必须使用 optimizer-dirs 配置选项指定 Zephir 在何处可以找到优化器。

"optimizer-dirs": [
    "optimizers"
]

Check the complete source code of this example here