Три часа ночи. Вношу последние правки в код и собираюсь завершать работу, но вдруг, посмотрев на огромный класс, который я правил и в котором отсутствовали типы данных (а IDE ругалась на это), я их быстренько проставил. А там, где тип возвращаемой переменной был неизвестен, стали работать автодополнения. Появился вопрос, а для чего? Разве только для удобства?

Для проверки того как типизация данных влияет, без особого фанатизма был сделан бенчмарк тест, под спойлером фрагмент кода.

Функции бенчмарка
// Тест 1: сложение
function addWithoutTypes($a, $b)
{
    return $a + $b;
}

function addWithTypes(int $a, int $b): int
{
    return $a + $b;
}

// Тест 2: работа с массивами
function processArrayWithoutTypes($arr)
{
    $sum = 0;
    foreach ($arr as $val) {
        $sum += $val * 2;
    }
    return $sum;
}

function processArrayWithTypes(array $arr): int
{
    $sum = 0;
    foreach ($arr as $val) {
        $sum += $val * 2;
    }
    return $sum;
}

// Тест 3: работа с объектами
class Calculator
{
    public function multiplyWithoutTypes($a, $b)
    {
        return $a * $b;
    }

    public function multiplyWithTypes(float $a, float $b): float
    {
        return $a * $b;
    }
}

// Тест 4: чуть сложные вычисления
function complexWithoutTypes($x, $y, $z)
{
    $a = $x * $y;
    $b = $y + $z;
    $c = $a / ($b ?: 1);
    return sqrt($c * $c + $x);
}

function complexWithTypes(float $x, float $y, float $z): float
{
    $a = $x * $y;
    $b = $y + $z;
    $c = $a / ($b ?: 1);
    return sqrt($c * $c + $x);
}

Тестовый стенд:

  • MacBook Pro 2018 x86_64 Intel i7-8559U (8) @ 2.70GHz;

  • PHP 8.4.6, к сожалению не помню метод установки, кажется компилировал из сорцев;

  • Каждый тест выполняется 10 миллионов итераций для получения статистически значимых результатов, предварительно прогревшись;

  • 5 конфигураций: без оптимизаций, только OPcache, OPcache + JIT (режимы 1205, 1235, 1255);

  • Тесты прогонял несколько раз для достоверности результата.

Настройки opcache являются стандартными, потому прикладывать я их сюда не буду.

Результаты меня удивили, первым пошёл тест без opcache и без jit

Тест без opcache и jit
=== PHP Configuration ===
OPcache Enabled: No
JIT Enabled: No

=== Running Benchmarks (10,000,000 iterations) ===

Test 1: Simple Addition
---------------------------------------------------------------------------
addWithoutTypes(5, 10)                   |   0.9140s |    91.40 ns/op
addWithTypes(5, 10)                      |   0.9100s |    91.00 ns/op
  → Result: WITH types is 0.44% faster

Test 2: Array Processing
---------------------------------------------------------------------------
processArrayWithoutTypes(array)          |  14.5909s |  1459.09 ns/op
processArrayWithTypes(array)             |  14.3597s |  1435.97 ns/op
  → Result: WITH types is 1.58% faster

Test 3: Class Methods
---------------------------------------------------------------------------
Calculator->multiplyWithoutTypes()       |   0.7837s |    78.37 ns/op
Calculator->multiplyWithTypes()          |   0.7714s |    77.14 ns/op
  → Result: WITH types is 1.56% faster

Test 4: Complex Calculations
---------------------------------------------------------------------------
complexWithoutTypes()                    |   1.4055s |   140.55 ns/op
complexWithTypes()                       |   1.4713s |   147.13 ns/op
  → Result: WITHOUT types is 4.68% faster


=== Summary ===
Total time WITHOUT types: 17.6941s
Total time WITH types:    17.5124s
Overall difference:       -1.03%

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

Далее прогнал включив opcache

Тест с opcache, но без jit
=== PHP Configuration ===
OPcache Enabled: Yes
JIT Enabled: No

=== Running Benchmarks (10,000,000 iterations) ===

Test 1: Simple Addition
---------------------------------------------------------------------------
addWithoutTypes(5, 10)                   |   0.9073s |    90.73 ns/op
addWithTypes(5, 10)                      |   0.8647s |    86.47 ns/op
  → Result: WITH types is 4.69% faster

Test 2: Array Processing
---------------------------------------------------------------------------
processArrayWithoutTypes(array)          |  14.5617s |  1456.17 ns/op
processArrayWithTypes(array)             |  15.0966s |  1509.66 ns/op
  → Result: WITHOUT types is 3.67% faster

Test 3: Class Methods
---------------------------------------------------------------------------
Calculator->multiplyWithoutTypes()       |   0.7711s |    77.11 ns/op
Calculator->multiplyWithTypes()          |   0.8225s |    82.25 ns/op
  → Result: WITHOUT types is 6.67% faster

Test 4: Complex Calculations
---------------------------------------------------------------------------
complexWithoutTypes()                    |   1.5212s |   152.12 ns/op
complexWithTypes()                       |   1.2378s |   123.78 ns/op
  → Result: WITH types is 18.63% faster


=== Summary ===
Total time WITHOUT types: 17.7613s
Total time WITH types:    18.0216s
Overall difference:       1.47%

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

Переходим к JIT, для начала немного о его конфиге.

opcache.jit = 1255
              ││││
              ││││
              │││└─ O: Optimization level (уровень оптимизации)
              ││└── T: Trigger (триггер компиляции)
              │└─── R: Register allocation (распределение регистров)
              └──── C: CPU-specific optimization (оптимизации для CPU)
Позиция 1: C - CPU-specific optimization

Значение

Описание

0

Отключены CPU-специфичные оптимизации

1

Включены оптимизации под конкретный CPU (AVX, SSE и т.д.)

Позиция 2: R - Register allocation

Значение

Описание

0

Не использовать регистры процессора

1

Линейное сканирование для выделения регистров

2

Глобальное выделение регистров

Позиция 3: T - Trigger (когда компилировать)

Значение

Описание

0

Компилировать весь скрипт сразу

1

Компилировать функцию при первом выполнении

2

Компилирование на основании профилирования

3

Профилирование на лету и компиляц��я

4

Компилирование при наличии @jit в PHPdoc

5

Трассировка горячих путей

Позиция 4: O - Optimization level

Значение

Описание

0

Без оптимизаций

1

Минимальные оптимизации

2

Выборочные оптимизации

3

Стандартные оптимизации

4

Агрессивные оптимизации

5

Максимальные оптимизации

На просторах сети были найдены популярные настройки, их и протестировал:

1 2 0 5
│ │ │ └─ 5: Максимальные оптимизации
│ │ └─── 0: Компилировать весь скрипт
│ └───── 2: Глобальное выделение регистров
└─────── 1: CPU-оптимизации включены
1 2 3 5
│ │ │ └─ 5: Максимальные оптимизации
│ │ └─── 3: Компилировать при первом запуске
│ └───── 2: Глобальное выделение регистров
└─────── 1: CPU-оптимизации включены
1 2 5 5
│ │ │ └─ 5: Максимальные оптимизации
│ │ └─── 5: Tracing - трассировка горячих путей
│ └───── 2: Глобальное выделение регистров
└─────── 1: CPU-оптимизации включены

А теперь к результатам теста:

Тест opcache jit 1205
=== PHP Configuration ===
OPcache Enabled: Yes
JIT Enabled: Yes (1205)

=== Running Benchmarks (10,000,000 iterations) ===

Test 1: Simple Addition
---------------------------------------------------------------------------
addWithoutTypes(5, 10)                   |   0.8090s |    80.90 ns/op
addWithTypes(5, 10)                      |   0.7151s |    71.51 ns/op
  → Result: WITH types is 11.60% faster

Test 2: Array Processing
---------------------------------------------------------------------------
processArrayWithoutTypes(array)          |   7.3388s |   733.88 ns/op
processArrayWithTypes(array)             |   4.5771s |   457.71 ns/op
  → Result: WITH types is 37.63% faster

Test 3: Class Methods
---------------------------------------------------------------------------
Calculator->multiplyWithoutTypes()       |   0.7012s |    70.12 ns/op
Calculator->multiplyWithTypes()          |   0.6463s |    64.63 ns/op
  → Result: WITH types is 7.83% faster

Test 4: Complex Calculations
---------------------------------------------------------------------------
complexWithoutTypes()                    |   1.0326s |   103.26 ns/op
complexWithTypes()                       |   0.9278s |    92.78 ns/op
  → Result: WITH types is 10.15% faster


=== Summary ===
Total time WITHOUT types: 9.8816s
Total time WITH types:    6.8664s
Overall difference:       -30.51%
Тест opcache jit 1235
=== PHP Configuration ===
OPcache Enabled: Yes
JIT Enabled: Yes (1235)

=== Running Benchmarks (10,000,000 iterations) ===

Test 1: Simple Addition
---------------------------------------------------------------------------
addWithoutTypes(5, 10)                   |   0.7993s |    79.93 ns/op
addWithTypes(5, 10)                      |   0.7152s |    71.52 ns/op
  → Result: WITH types is 10.53% faster

Test 2: Array Processing
---------------------------------------------------------------------------
processArrayWithoutTypes(array)          |   7.8423s |   784.23 ns/op
processArrayWithTypes(array)             |   4.8758s |   487.58 ns/op
  → Result: WITH types is 37.83% faster

Test 3: Class Methods
---------------------------------------------------------------------------
Calculator->multiplyWithoutTypes()       |   0.6806s |    68.06 ns/op
Calculator->multiplyWithTypes()          |   0.6428s |    64.28 ns/op
  → Result: WITH types is 5.56% faster

Test 4: Complex Calculations
---------------------------------------------------------------------------
complexWithoutTypes()                    |   1.0316s |   103.16 ns/op
complexWithTypes()                       |   0.9201s |    92.01 ns/op
  → Result: WITH types is 10.81% faster


=== Summary ===
Total time WITHOUT types: 10.3538s
Total time WITH types:    7.1539s
Overall difference:       -30.91%
Тест opcache jit 1255
=== PHP Configuration ===
OPcache Enabled: Yes
JIT Enabled: Yes (1255)

=== Running Benchmarks (10,000,000 iterations) ===

Test 1: Simple Addition
---------------------------------------------------------------------------
addWithoutTypes(5, 10)                   |   0.7502s |    75.02 ns/op
addWithTypes(5, 10)                      |   0.6914s |    69.14 ns/op
  → Result: WITH types is 7.84% faster

Test 2: Array Processing
---------------------------------------------------------------------------
processArrayWithoutTypes(array)          |   3.7918s |   379.18 ns/op
processArrayWithTypes(array)             |   3.5890s |   358.90 ns/op
  → Result: WITH types is 5.35% faster

Test 3: Class Methods
---------------------------------------------------------------------------
Calculator->multiplyWithoutTypes()       |   0.7375s |    73.75 ns/op
Calculator->multiplyWithTypes()          |   0.6369s |    63.69 ns/op
  → Result: WITH types is 13.65% faster

Test 4: Complex Calculations
---------------------------------------------------------------------------
complexWithoutTypes()                    |   1.0218s |   102.18 ns/op
complexWithTypes()                       |   0.9487s |    94.87 ns/op
  → Result: WITH types is 7.15% faster


=== Summary ===
Total time WITHOUT types: 6.3013s
Total time WITH types:    5.8659s
Overall difference:       -6.91%

Результат очень порадовал, типизация колоссально ускоряет время выполнения кода. Дополнительно JIT ускорил работу функций которые не были типизированны за счет предугадывая типов и спекулятивных оптимизаций, это когда JIT генерирует код для определённых типов данных, но с проверкой типизации.

Для наглядности привожу графики, так как в текстовом формате воспринимать такое кол-во чисел занятие не из приятных

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

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

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

Комментарии (2)


  1. d3d11
    14.11.2025 03:38

    В PHP много чего нужно еще, и типизация в этом числе.


  1. Lainhard
    14.11.2025 03:38

    Появился вопрос, а для чего? Разве только для удобства?

    Да. В этом вся суть. В удобстве, а если точнее: в "неудобстве выстрелить себе в лицо".