自定义优化器

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