Оптимизации

Поскольку код на Zephir иногда очень высокоуровневый, Си-компилятор может быть не в состоянии эффективно оптимизировать этот код.

Благодаря AOT-компилятору (ahead-of-time), Zephir способен оптимизировать код во время компиляции, потенциально улучшая время выполнения или уменьшая объем памяти, необходимый программе.

Вы можете включить оптимизацию, передав её название при помощи ключа -f:

zephir -fstatic-type-inference -flocal-context-pass

Оптимизация может быть отключена при помощи ключа -fno-:

zephir -fno-static-type-inference -fno-call-gatherer-pass

Оптимизации также могут настроены в конфигурационном файле config.json, как показано ниже:

{
  "namespace": "mae",
  "name": "My Awesome Extension",
  "author": "ACME",
  "version": "1.0.0",

  "optimizations": {
    "static-type-inference": true,
    "static-type-inference-second-pass": true,
    "local-context-pass": true,
    "constant-folding": true,
    "static-constant-class-folding": true,
    "call-gatherer-pass": true,
    "check-invalid-reads": false,
    "private-internal-methods": false,
    "public-internal-methods": false,
    "public-internal-functions": true
  }
}

Поддерживаются следующие типы оптимизаций:

call-gatherer-pass

Эта оптимизации учитывает, сколько раз функция или метод вызывается в рамках одного и того же метода. Это позволяет компилятору использовать встроенное кеширование, что позволяет избежать повторного поиска владельца метода или функции:

class MyClass extends OtherClass
{

    public function getValue()
    {
        this->someMethod();

        // Этот метод может быть вызван быстрее
        this->someMethod();
    }
}

check-invalid-reads

Этот флаг форсирует принудительную проверку типов во время компиляции на выявление неверных операций чтения. Это гарантирует, что все переменные и указатели правильно определены и инициализированы значениями по умолчанию. Например, сравните:

namespace Acme;

class ForInRange
{
    public static function forEmpty(var n)
    {
        var i;
        for i in range(1, n) {
            // Здесь происходит полезная работа
        }
    }
}

со следующим примером:

namespace Acme;

class ForInRange
{
    public static function forEmpty(var n)
    {
        var i = null;
        for i in range(1, n) {
            // Здесь происходит полезная работа
        }
    }
}

Оба примера в действительности являются корректными с точки зрения синтаксиса Zephir. Разница проявляется при генерации Си-кода:

zval *n;

// ...

zephir_fetch_params(1, 1, 0, &n);

сравните с:

zval *n = NULL;

// ...

zephir_fetch_params(1, 1, 0, &n);

Хорошей практикой для любого языка программирования является инициализация переменных значениями по умолчанию с соблюдением корректности типов. Если не следовать этому принципу, потенциально это может привести к непредвиденным последствиям для приложения, а также может привести к ошибкам, утечкам памяти и т.д. Используя флаг check-invalid-reads в config.json, мы гарантируем, что указатели правильно инициализированы вместе с соответствующими переменными в Си-коде. Zephir-разработчики не увидят изменений в коде. Эта оптимизация влияет на сгенерированный Си-код.

Более подробную информацию о том, почему указатели в Си необходимо обнулять, можно найти здесь.

constant-folding

Свертывание констант — это процесс упрощения константных выражений во время компиляции. Следующий код упрощается, когда эта оптимизация включена:

public function getValue()
{
    return (86400 * 30) / 12;
}

Преобразуется в:

public function getValue()
{
    return 216000;
}

internal-call-transformation

Флаг internal-call-transformation используется для генерации внутренних методов, на основе их эквивалентов в PHP, что позволяет обойти пространство PHP для этих внутренних методов. По умолчанию, эта оптимизация отключена.

Эта оптимизация генерирует 2 реализации для каждого метода, одна из которых представлена в PHP, другая - внутренняя.

Исключением / ограничением из вышеизложенного являются следующие ситуации:

  • Можно заменить только PHP методы (например, мы не можем сделать это для методов Phalcon)
  • Замыкания (__invoke) и __construct не поддерживаются
  • Количество требуемых параметров должно точно соответствовать количеству реальных параметров
  • Не работает для ZendEngine2 (PHP 5.6)

local-context-pass

Этот этап компиляции перемещает переменные, которые размещены в куче, в стек. Эта оптимизация может уменьшить количество косвенных обращений к памяти, которые должна выполнять программа.

static-constant-class-folding

Эта оптимизация заменяет значения констант класса во время компиляции:

class MyClass
{

    const SOME_CONSTANT = 100;

    public function getValue()
    {
        return self::SOME_CONSTANT;
    }
}

Преобразуется в:

class MyClass
{

    const SOME_CONSTANT = 100;

    public function getValue()
    {
        return 100;
    }
}

static-type-inference

Этот этап компиляции очень важен, поскольку он ищет динамические переменные, которые потенциально могут быть преобразованы в статические/примитивные типы, которые лучше оптимизируются базовым компилятором.

Следующий код использует набор динамических переменных для выполнения некоторых математических вычислений:

public function someCalculations(var a, var b)
{
    var i = 0, t = 1;

    while i < 100 {
        if i % 3 == 0 {
            continue;
        }
        let t += (a - i), i++;
    }

    return i + b;
}

Переменные a, b и i используются исключительно для математических операций, и таким образом их можно преобразовать в статические переменные, используя преимущества других этапов компиляции. После выполнения компилятор автоматически переписывает этот код в:

public function someCalculations(int a, int b)
{
    int i = 0, t = 1;

    while i < 100 {
        if i % 3 == 0 {
            continue;
        }
        let t += (a - i), i++;
    }

    return i + b;
}

Отключив этот этап компиляции, все переменные будут поддерживать тип, с которым они были первоначально объявлены, без оптимизации.

static-type-inference-second-pass

Эта оптимизация включает повторный вывод типов, что в целом улучшает работу проделанную на основе данных, собранных при первом проходе вывода типов.