• 自定义优化器

    自定义优化器

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

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

    在 Zephir的作用优化器类名优化器路径C 中的函数
    calculate_piCalculatePiOptimizeroptimizers/CalculatePiOptimizer.phpmy_calculate_pi

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

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

    1. <?php
    2. namespace Zephir\Optimizers\FunctionCall;
    3. use Zephir\Call;
    4. use Zephir\CompilationContext;
    5. use Zephir\Compiler\CompilerException;
    6. use Zephir\Optimizers\OptimizerAbstract;
    7. class CalculatePiOptimizer extends OptimizerAbstract
    8. {
    9. public function optimize(array $expression, Call $call, CompilationContext $context)
    10. {
    11. //...
    12. }
    13. }

    优化器的实现在很大程度上取决于要生成的代码类型。 在我们的示例中, 我们将用对 c 函数的调用来替换对此函数的调用。 在 Zephir 中, 用于调用此函数的代码是:

    1. let pi = calculate_pi(1000);

    因此, 优化器只需要一个参数, 我们必须验证这一点, 以避免以后出现问题:

    1. <?php
    2. public function optimize(array $expression, Call $call, CompilationContext $context)
    3. {
    4. if (!isset($expression['parameters'])) {
    5. throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    6. }
    7. if (count($expression['parameters']) > 1) {
    8. throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    9. }
    10. //...
    11. }

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

    1. <?php
    2. public function optimize(array $expression, Call $call, CompilationContext $context)
    3. {
    4. if (!isset($expression['parameters'])) {
    5. throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    6. }
    7. if (count($expression['parameters']) > 1) {
    8. throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    9. }
    10. /**
    11. * Process the expected symbol to be returned
    12. */
    13. $call->processExpectedReturn($context);
    14. $symbolVariable = $call->getSymbolVariable();
    15. if (!$symbolVariable->isDouble()) {
    16. throw new CompilerException("Calculated PI values only can be stored in double variables", $expression);
    17. }
    18. //...
    19. }

    我们正在检查返回的值是否将存储在 double 类型的变量中; 否则, 将引发编译器异常。

    接下来我们需要做的是处理传递给函数的参数:

    1. <?php
    2. $resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression);

    Zephir 的一个好做法是创建不修改其参数的函数。 如果要更改传递的参数, Zephir 将需要为其分配内存, 并且必须使用 getResolvedParams 而不是 getReadOnlyResolvedParams

    这些方法返回的代码是有效的 c 代码, 可在代码打印机中用于生成 c 函数调用:

    1. <?php
    2. // Generate the C-code
    3. return new CompiledExpression('double', 'calculate_pi( ' . $resolvedParams[0] . ')', $expression);

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

    完整的优化器代码是:

    1. <?php
    2. namespace Zephir\Optimizers\FunctionCall;
    3. use Zephir\Call;
    4. use Zephir\CompilationContext;
    5. use Zephir\CompiledExpression;
    6. use Zephir\Compiler\CompilerException;
    7. use Zephir\Optimizers\OptimizerAbstract;
    8. class CalculatePiOptimizer extends OptimizerAbstract
    9. {
    10. public function optimize(array $expression, Call $call, CompilationContext $context)
    11. {
    12. if (!isset($expression['parameters'])) {
    13. throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    14. }
    15. if (count($expression['parameters']) > 1) {
    16. throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    17. }
    18. /**
    19. * Process the expected symbol to be returned
    20. */
    21. $call->processExpectedReturn($context);
    22. $symbolVariable = $call->getSymbolVariable();
    23. if (!$symbolVariable->isDouble()) {
    24. throw new CompilerException("Calculated PI values only can be stored in double variables", $expression);
    25. }
    26. $resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression);
    27. return new CompiledExpression('double', 'my_calculate_pi(' . $resolvedParams[0] . ')', $expression);
    28. }
    29. }

    实现函数my_calculate_pi的代码是用C编写的,必须与扩展一起编译。

    这段代码必须放在ext/目录中任何您认为合适的位置; 检查这些文件是否与Zephir生成的文件冲突。

    这个文件必须包含Zend Engine标头,以及函数的C实现:

    1. #ifdef HAVE_CONFIG_H
    2. #include "config.h"
    3. #endif
    4. #include "php.h"
    5. #include "php_ext.h"
    6. double my_calculate_pi(zval *accuracy) {
    7. return 0.0;
    8. }

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

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

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

    1. "optimizer-dirs": [
    2. "optimizers"
    3. ]

    Check the complete source code of this example here