Долгожданный выход Zephir 0.7 Beta
Вот и наступило долгожданное время выхода версии 0.7 и официального перехода проекта в состояние Beta. Новая версия несет более чем 30 исправлений и улучшений.
Новый модификатор Internal для методов
Модификатор internal позволяет сделать из нашего PHP метода полноценный аналог Си-функции для ускорения производительности.
Напишем простой класс с методом реализующий нахождения чисел Фибоначчи:
class McallInternal
{
/**
* Обратите внимания на рекурсию
*/
internal function fibonacci(long n) -> long
{
if n == 1 || n == 2 {
return 1;
}
return this->fibonacci(n - 1) + this->fibonacci(n - 2);
}
public function callFibonacci(long n) -> long
{
return this->fibonacci(n);
}
}Напишем аналог на PHP, и запустим оба варианта:
<?php
class Math
{
function fibonacci($n)
{
if ($n == 1 || $n == 2) {
return 1;
}
return $this->fibonacci($n - 1) + $this->fibonacci($n - 2);
}
}
$t = microtime(true);
echo (new Test\Mcallinternal)->callFibonacci(31), PHP_EOL;
echo 'Zephir: ', microtime(true) - $t, PHP_EOL;
$t = microtime(true);
echo (new Math)->fibonacci(31), PHP_EOL;
echo 'PHP: ', microtime(true) - $t, PHP_EOL;Результаты:
Zephir: 0.12126803398132
PHP 5.6: 0.613104820251464
Zephir: 0.12285614013672
PHP 5.6: 0.55841708183289
4.5/5x 450%/500% improvement
Под капотом
Почему такой выигрыш? Давайте разбираться. Обычный PHP land метод сгенерированный Zephir будет выглядить таким образом:
PHP_METHOD(Test_McallInternal, other) {
zval *a_param = NULL, *b_param = NULL;
long a, b;
//Парсинг параметров и стэка
zephir_fetch_params(0, 2, 0, &a_param, &b_param);
// Перевод значение zval в long
a = zephir_get_intval(a_param);
b = zephir_get_intval(b_param);
RETURN_DOUBLE(zephir_safe_div_long_long(a, b TSRMLS_CC));
}Вызов PHP land метода:
// Слот кэша
zephir_fcall_cache_entry *_6 = NULL;
// Флаг статуса функции после вызова, что бы понять есть ли exception?
int ZEPHIR_LAST_CALL_STATUS;
// Результат работы функции
zval *_result = NULL,
// Параметры 1 и 2
zval *_parameter1 = NULL, *_5 = NULL;
//Необходимо выделить память под первый параметр
ZEPHIR_INIT_NVAR(_parameter1);
//Положим значение в структуры zval
ZVAL_LONG(_parameter1, 1);
//И не забыть про второй параметр ;)
ZEPHIR_INIT_NVAR(_parameter2);
//Положим значение в структуры zval
ZVAL_LONG(_parameter2, 2);
/**
* Динамически вызой метода
* this_ptr это zend_class_entry на текущий класс
*/
ZEPHIR_CALL_METHOD(&_result, this_ptr, "other", &_6, 0, _parameter1, _parameter2);
// Проверяем из контекста какой код вернул метод
zephir_check_call_status();Как уже стало видно, тут мы видим очень большой оверхед на вызовах. Ну а что же будет с использование модификатора internal:
static void zep_Test_McallInternal_other(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used, zval *a_param_ext, zval *b_param_ext TSRMLS_DC) {
zval *a_param = NULL, *b_param = NULL;
long a, b;
/**
* Надеюсь gcc/clang не такие дураки как мы ;)
*/
a_param = a_param_ext;
b_param = b_param_ext;
// Перевод значение zval в long
a = zephir_get_intval(a_param);
b = zephir_get_intval(b_param);
RETURN_DOUBLE(zephir_safe_div_long_long(a, b TSRMLS_CC));
}Ну и сам вызов internal метода:
// Результат работы функции - HEAP
zval *_result = NULL;
// Параметры в структуре zval но уже не heap а stack
zval parameter1 = zval_used_for_init, parameter2 = zval_used_for_init;
// Флаг статуса функции после вызова, что бы понять есть ли exception?
int ZEPHIR_LAST_CALL_STATUS;
//Чистим пред значение в стэке
ZEPHIR_SINIT_NVAR(parameter1);
//Новое значение
ZVAL_LONG(&_4, 1);
//Чистим пред значение в стэке
ZEPHIR_SINIT_NVAR(_5);
//Новое значение
ZVAL_LONG(&_5, 2);
/**
* Вызой нативной статичной функции
* this_ptr это zend_class_entry на текущий класс
*
* Внимание! Параметры передаются указателями созданными на основе stack переменных
* stack &a -> (zval *a)
*/
ZEPHIR_CALL_INTERNAL_METHOD_P2(&_result, this_ptr, zep_Test_McallInternal_other, ¶meter1, ¶meter2);
// Проверяем из контекста какой код вернул метод
zephir_check_call_status();Различия между internal методом? В internal:
- Нет передачи параметров по стеку через Zend, нативный Си вызов.
- Нет получения параметров из стека.
- Нет магии с контекстом.
- Нет динамического слотового кэша, так как не нужен.
- Параметры для вызова
internalметода лежат в Stack (Stack быстрее HEAP).
Реализован глобальный статический кэш
Предыдущий кэш, реализованный в Zephir, имел проблемы с PHP-FPM или с функциями/методами, которые были переопределены. Ну и, конечно же, не был доступен в Windows. Новый кэш работает на стратегии слотов (slot-based cache), функции или методы, который являются кандидатами на кэширование, будут кэшированы в глобальном индексируемом кэше который работает намного быстрее чем кэш на статичных переменных (static-variable based cache)
Работает оптимально для:
- Final/private методов
- Функций из ядра PHP
На замерах дает выигрыш порядка 5-10%
https://github.com/phalcon/zephir/pull/956
Реализован Low overhead function call for PHP 5.6
Для тестирования возьмем код:
public static function guid()
{
return sprintf(
"%04X%04X-%04X-%04X-%04X-%04X%04X%04X",
rand(0, 65535), rand(0, 65535),
rand(0, 65535), rand(16384, 20479),
rand(32768, 49151), rand(0, 65535),
rand(0, 65535), rand(0, 65535)
);
}Протестируем на производительность (выполним 1'000.000 раз):
v0.6.2 3289 milliseconds
v0.6.3 2149 milliseconds
65% improvement
На замерах дает выигрыш порядка 40-80%
https://github.com/phalcon/zephir/pull/958
Улучшения
- Low overhead fcall (only PHP 5.6)
- Add initializer (RINIT) support to code generation
- Added Build date to ext info
- Api zephir theme's title is now customizable through theme options
- Fix unlikely and likely to also work on variables
- Call parent constructors if zephir is the origin of the childs constructor
- Uncomment ternary complex tests
- Fix #905: Rework how and when [on unitialize] properties are initialized
- Fix #665: Ensure that the ternary operator promotes temporary variables
- Fix #537: Make sure class properties are initialized in the right order
- Fix optimizers to init symbolVariable as late as possible
- Implement arrays as default values of static properties
- Fix branching of superglobals by fetching them as soon as possible [Root]
- Add checks for builtin [user defined] functions to reduce warnings
- Fixes #960
- Fixed initialization of slot cache in 64bits
- Only using specialized function in PHP 5.6
- Allow case statements contains eval-expressions, fixes #768
- Support built-in PHP classes static-constant-access
- CaseOperator - implement [int] case from string type
- Fix #830, added [int] case from variable - string
- Fix Redis prototype
- Prototype\Redis - added new methods: get, flushDB, set, delete and new consts
- Fix bug about GreaterEqualOperator
- Remove forgotten echo for Windows build
- Allow Method and FunctionCache to also receive null-values.
- Prototype\Memcached - added methods: set, delete, flush, get
- Disabling static nts for all versions
- STA Mcall array can be a callable type
- Support Passes for istring
- Fix #679: Check for local variables and treat them accordingly
- Fix superglobal-access not to cause warnings, since they are initialized
- Added stricter parameter types.
- Fix fcall info keys to not collide with functions for static calls
- Fixed #740 issue
- Fix #915 issue, fetch properties for Runtime Class Definition
- Fix zephir_floor and zephir_ceil to be consistent and not dangerous to use
Исправления
- Internal functions: Implement static call, fix unused return value calls
- Fixed division using double, returning double from zephir_floor
- Fixed zephir_floor optimizer
- Add checks for builtin [user defined] functions to reduce warnings
- Fix branching of superglobals by fetching them as soon as possible
- https://github.com/phalcon/zephir/commit/00118e6202da68895656f6fcd2f60793550d07d6