Search
Write a publication
Pull to refresh

Comments 14

Когда пространства имён только появились, писал глобальные функции со слэшами. Через какое-то время перестал. Ещё через какое-то время открыл тот свой код, который был со слэшами и блеванул на монитор. Больше не использовал их. Статью же не читал, извините.

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

Интересно, зачем люди вообще пишут substr($var, 0, 1), если можно просто $var[0]?

Не надо. Надо, только если у вас символы используемые в строках выходят за границы ASCII.

Для 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(). Просто оставлю ссылку посвежее здесь.

Спасибо за ссылку, почитаю, может и статью дополню, если будет чем.

Давайте не забывать, что:

  1. Проверяем 4 функции, получая +7% с нифига

  • На реальном проекте 100500 вызовов функций, но при сохранении соотношения (в 7%) получим уже другой результат (ну например 0.9с вместо 1с)

  • В случае же циклов - разница может достигать до 20%+ (на практике лексер так ускорил)

  1. В php есть и другие оптимизации (интринсики), например как в случае strlen выше или проверки на тип (is_null, is_int, etc) и прочие.

  • В случае strlen, а особенно если значение константное - \strlen($immutableString), то в коде не будет никаких вызовов функций, а просто прямая замена сразу на вычисленный заранее результат.

  • Если бекслеш отсутсвует, то PHP не может определить является ли эта функция "глобальной" или пользовательской, а следовательно все оптимизации игнорируются.

  1. Что касается других интринсиков, то разница в исполняемом коде может разнИца, например, в 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) - могут предупредить о потенциальных конфликтах имен или неоднозначных вызовах.

Sign up to leave a comment.

Articles