Оптимизации
Поскольку код на 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
Эта оптимизация включает повторный вывод типов, что в целом улучшает работу проделанную на основе данных, собранных при первом проходе вывода типов.