Comments 14
Когда пространства имён только появились, писал глобальные функции со слэшами. Через какое-то время перестал. Ещё через какое-то время открыл тот свой код, который был со слэшами и блеванул на монитор. Больше не использовал их. Статью же не читал, извините.
Статью не читал, ибо неохота. Слэши перед функциями не ставлю, максимум укажу через use.
Интересно, зачем люди вообще пишут substr($var, 0, 1), если можно просто $var[0]?
Для CodeSniffer есть более современный slevomat/coding-standard. У самого CodeSniffer сменился мейнтейнер и разработка пошла более быстро.
В статье я ожидал функции, которые компилятор оптимизирует в опкоды, вот статья например, устаревшая немного потому что там нет sprintf, без этого всё это экономия на спичках
Интересно, что практически одновременно с этой статьёй, с разницей в одну минуту, на Реддите был опубликован пост ровно на ту же тему, но с полностью противоположными выводами!
И приводится условие, при котором разницу (весьма значительную), можно увидеть, и которое не выполнено в приведённом здесь тесте:
opcache ❌
Но что ещё интереснее - если здешний тест выполнить при включённом опкеше, то разницы всё равно нет...
В общем, там выяснилось, что как и всегда, порядок цифр пропорционален кривизне рук тестировщика. Магия опкеша заключалась в том, что он разумно кешировал сразу результат(!) вызова функции, благодаря константному аргументу. А у исправленного теста результат оказался гораздо скромнее, вплоть до неразличимости. Так что ближе к истине оказался тезис в этой статье, а не в той.
Единственно что, для определённого списка функций подстановка бэкслеша таки позволяет заметить разницу невооружённым глазом. К примеру, замена тела функции на
return strlen($firstName) + strlen($middleName) + strlen($lastName);
Даёт мне такой результат
benchFormatUserName1....................R3 I3 - Mo0.055μs (±1.84%)
benchFormatUserName2....................R5 I4 - Mo0.036μs (±0.00%)
Единственно что, для определённого списка функций подстановка бэкслеша таки позволяет заметить разницу невооружённым глазом.
Спасибо за ссылку. По ней в конце списка функций указано, что он актуален для PHP 8.1. Ссылка на GitHub в качестве источника ведёт в файл zend_compile.c
, который уже успел измениться с тех пор, как и список функций PHP, для которых используется специальная обработка. Все эти функции перечислены в zend_try_compile_special_func_ex(). Просто оставлю ссылку посвежее здесь.
Спасибо за ссылку, почитаю, может и статью дополню, если будет чем.
Давайте не забывать, что:
Проверяем 4 функции, получая +7% с нифига
На реальном проекте 100500 вызовов функций, но при сохранении соотношения (в 7%) получим уже другой результат (ну например 0.9с вместо 1с)
В случае же циклов - разница может достигать до 20%+ (на практике лексер так ускорил)
В php есть и другие оптимизации (интринсики), например как в случае strlen выше или проверки на тип (is_null, is_int, etc) и прочие.
В случае
strlen
, а особенно если значение константное -\strlen($immutableString)
, то в коде не будет никаких вызовов функций, а просто прямая замена сразу на вычисленный заранее результат.Если бекслеш отсутсвует, то PHP не может определить является ли эта функция "глобальной" или пользовательской, а следовательно все оптимизации игнорируются.
Что касается других интринсиков, то разница в исполняемом коде может разнИца, например, в 40 раз.
P.S. Выше в замерах у @FanatPHP отличия в скорости одного из интринсиков почти в ~2 раза, но чисто математически и если отбросить всякие L1/2/3 кеши, предсказания и прочее, как в примере кода ниже, она может отличаться в несколько десятков.
Пример очень простого кода
Код
<?php
namespace Example;
$a = 23;
\is_null($a);
Вывод
JIT$/home/test.php: ; (/home/test.php)
call 0x55b6ce3cc7e0
mov $EG(exception), %rax
cmp $0x0, (%rax)
jnz JIT$$exception_handler
jmp ZEND_RETURN_SPEC_CONST_LABEL
А это без бекслеша
Код
<?php
namespace Example;
$a = 23;
is_null($a); // Просто убрали слеш
Вывод
JIT$/home/test.php: ; (/home/test.php)
call 0x556ad05cc7e0
mov $EG(exception), %rax
cmp $0x0, (%rax)
jnz JIT$$exception_handler
mov 0x40(%r14), %rdx
mov 0x8(%rdx), %rax
test %rax, %rax
jz .L14
.L1:
test $0x1, (%rax)
mov $0x60, %rdi
jnz .L2
mov $0x1, %edx
cmp 0x20(%rax), %edx
cmova 0x20(%rax), %edx
sub 0x48(%rax), %edx
sub 0x40(%rax), %edx
shl $0x4, %edx
movsxd %edx, %rdx
sub %rdx, %rdi
.L2:
mov $EG(vm_stack_top), %r15
mov (%r15), %r15
mov $EG(vm_stack_end), %rdx
mov (%rdx), %rdx
sub %r15, %rdx
cmp %rdi, %rdx
jb .L15
mov $EG(vm_stack_top), %rdx
add %rdi, (%rdx)
mov $0x0, 0x28(%r15)
mov %rax, 0x18(%r15)
.L3:
mov $0x0, 0x20(%r15)
mov $0x1, 0x2c(%r15)
mov $0x0, 0x30(%r15)
mov %r15, 0x8(%r14)
mov 0x18(%r15), %rax
test $0x300, (%rax)
jnz .L16
cmp $0x0, 0x58(%r14)
jz .L20
lea 0x50(%r14), %rdi
cmp $0xa, 0x8(%rdi)
jnz .L4
mov (%rdi), %rdi
add $0x8, %rdi
.L4:
mov (%rdi), %rdx
mov %rdx, 0x50(%r15)
mov 0x8(%rdi), %eax
mov %eax, 0x58(%r15)
test %ah, %ah
jz .L5
add $0x1, (%rdx)
.L5:
mov $0x556ac08d4770, %rax
mov %rax, (%r14)
mov $0x0, 0x8(%r14)
mov %r14, 0x30(%r15)
mov 0x18(%r15), %rax
cmp $0x2, (%rax)
jnz .L10
mov $0x0, 0x8(%r15)
mov $0x0, 0x10(%r15)
mov 0x38(%rax), %rdx
test $0x1, %rdx
jz .L6
mov $CG(map_ptr_base), %rcx
add (%rcx), %rdx
mov (%rdx), %rdx
.L6:
mov %rdx, 0x40(%r15)
mov $EG(current_execute_data), %rcx
mov %r15, (%rcx)
mov %r15, %r14
mov 0x50(%rax), %r15
mov 0x20(%rax), %edx
mov 0x2c(%r14), %ecx
cmp %edx, %ecx
jg .L21
test $0x100, 0x4(%rax)
jnz .L7
mov %ecx, %edx
shl $0x5, %rdx
add %rdx, %r15
.L7:
mov 0x48(%rax), %edx
sub %ecx, %edx
jle .L9
shl $0x4, %rcx
lea 0x50(%r14,%rcx), %rcx
.L8:
mov $0x0, 0x8(%rcx)
sub $0x1, %edx
lea 0x10(%rcx), %rcx
jnz .L8
.L9:
jmp (%r15)
.L10:
test $0x800, 0x4(%rax)
jnz .L22
.L11:
mov $EG(current_execute_data), %rcx
mov %r15, (%rcx)
mov %rsp, %rsi
mov $0x1, 0x8(%rsi)
mov %r15, %rdi
call 0x48(%rax)
mov $EG(current_execute_data), %rax
mov %r14, (%rax)
mov %r15, %rdi
mov $zend_jit_vm_stack_free_args_helper, %rax
call *%rax
test $0x4, 0x2a(%r15)
jnz .L23
mov $EG(vm_stack_top), %rax
mov %r15, (%rax)
.L12:
test $0x1, 0x9(%rsp)
jnz .L24
.L13:
mov $EG(exception), %rax
cmp $0x0, (%rax)
jnz JIT$$icall_throw
mov $EG(vm_interrupt), %rax
cmp $0x0, (%rax)
jnz .L27
mov $0x556ac08d4790, %r15
jmp .ENTRY1
.ENTRY1:
jmp ZEND_RETURN_SPEC_CONST_LABEL
.L14:
mov $0x556ac08d46d0, %rdi
lea 0x8(%rdx), %rsi
mov $zend_jit_find_ns_func_helper, %rax
call *%rax
test %rax, %rax
jnz .L1
mov %r15, (%r14)
jmp JIT$$undefined_function
.L15:
mov %rax, %rsi
mov $0x556ac08d4730, %rax
mov %rax, (%r14)
mov $zend_jit_extend_stack_helper, %rax
call *%rax
mov %rax, %r15
jmp .L3
.L16:
cmp $0x0, 0x58(%r14)
jnz .L17
mov $0x1, 0x58(%r14)
jmp .L18
.L17:
cmp $0xa, 0x58(%r14)
jnz .L18
mov 0x50(%r14), %rcx
add $0x1, (%rcx)
mov %rcx, 0x50(%r15)
mov $0x10a, 0x58(%r15)
jmp .L19
.L18:
call _emalloc_32
mov $0x2, (%rax)
mov $0x1a, 0x4(%rax)
mov $0x0, 0x18(%rax)
mov 0x50(%r14), %rdx
mov %rdx, 0x8(%rax)
mov 0x58(%r14), %ecx
mov %ecx, 0x10(%rax)
mov %rax, 0x50(%r14)
mov $0x10a, 0x58(%r14)
mov %rax, 0x50(%r15)
mov $0x10a, 0x58(%r15)
.L19:
jmp .L5
.L20:
mov $0x556ac08d4750, %rax
mov %rax, (%r14)
mov $0x50, %edi
mov $zend_jit_undefined_op_helper, %rax
call *%rax
mov $0x1, 0x58(%r15)
test %rax, %rax
jz JIT$$exception_handler
jmp .L5
.L21:
mov $zend_jit_copy_extra_args_helper, %rax
call *%rax
mov 0x18(%r14), %rax
mov 0x2c(%r14), %ecx
jmp .L7
.L22:
mov $zend_jit_deprecated_helper, %rax
call *%rax
test %al, %al
mov 0x18(%r15), %rax
jnz .L11
jmp JIT$$exception_handler
.L23:
mov %r15, %rdi
mov $zend_jit_free_call_frame, %rax
call *%rax
jmp .L12
.L24:
mov (%rsp), %rdi
sub $0x1, (%rdi)
jnz .L25
mov $0x556ac08d4770, %rax
mov %rax, (%r14)
call rc_dtor_func
jmp .L13
.L25:
cmp $0xa, 0x8(%rsp)
jnz .L26
test $0x2, 0x11(%rdi)
jz .L13
mov 0x8(%rdi), %rdi
.L26:
test $0xfffffc10, 0x4(%rdi)
jnz .L13
call gc_possible_root
jmp .L13
.L27:
mov $0x556ac08d4790, %r15
jmp JIT$$interrupt_handler
Все супер! а я бы еще добавил к статьи просто упоминание, когда это не обязательно, чтоб разжевать уже до конца:)
- В глобальном пространстве имен: Если файл не содержит namespace ...;
в начале, то все функции и константы по умолчанию ищутся в глобальном пространстве. strlen()
и \strlen()
будут работать одинаково.
- Для функций, импортированных через use function
(PHP 5.6+):
namespace MyApp;
use function \json_encode;
function doSomething() {
json_encode(...); // Вызов импортированной глобальной функции без \
}
namespace MyApp\Utils;
function json_encode($data) {
// ... кастомная реализация (плохая практика для такого имени, но технически возможно и даже попадался на такое) ...
}
function process() {
$data = ['a' => 1];
// Вызов КАСТОМНОЙ функции json_encode из текущего пространства MyApp\Utils:
$resultCustom = json_encode($data);
// Вызов ВСТРОЕННОЙ PHP функции json_encode (с помощью `\`):
$resultBuiltin = \json_encode($data);
// ... разные результаты ...
}
А самое главное Статические анализаторы (Psalm, PHPStan) - могут предупредить о потенциальных конфликтах имен или неоднозначных вызовах.
Нужно ли в PHP перед вызовом функций ставить обратный слэш?