Еще один бенчмарк PHP

RedDeveloper
24.03.2023 23:42
Еще один бенчмарк PHP

Сегодня я наткнулся на забавный пост на r/ProgrammerHumor, который заставил меня задуматься об одной вещи, которая меня всегда интересовала. Действительно ли важно, какие утверждения мы используем? Или читабельность и связность важнее?

Для первого теста я решил сравнить между собой операторы if-else и switch, используя 100 000 итераций для каждого.

Что касается второго теста, я сравниваю циклы. Соответственно for, while и foreach при тех же условиях.

Создание первой игровой площадки

Для условных блоков я хочу сравнить их с числовыми значениями, строками и смесью между ними.

Сценарий 1: Числовые значения

function test_numeric_if_else($value) {
    if ($value === 1) {
        return "One";
    } elseif ($value === 2) {
        return "Two";
    } elseif ($value === 3) {
        return "Three";
    } else {
        return "Other";
    }
}

function test_numeric_switch($value) {
    switch ($value) {
        case 1:
            return "One";
        case 2:
            return "Two";
        case 3:
            return "Three";
        default:
            return "Other";
    }
}

Сценарий 2: Строки

function test_string_if_else($value) {
    if ($value === "apple") {
        return "Fruit";
    } elseif ($value === "carrot") {
        return "Vegetable";
    } elseif ($value === "cookie") {
        return "Dessert";
    } else {
        return "Unknown";
    }
}

function test_string_switch($value) {
    switch ($value) {
        case "apple":
            return "Fruit";
        case "carrot":
            return "Vegetable";
        case "cookie":
            return "Dessert";
        default:
            return "Unknown";
    }
}

Сценарий 3: смешанные значения

function test_mixed_if_else($value) {
    if ($value === 1) {
        return "One";
    } elseif ($value === "two") {
        return "Two";
    } elseif ($value === 3.0) {
        return "Three";
    } else {
        return "Other";
    }
}

function test_mixed_switch($value) {
    switch ($value) {
        case 1:
            return "One";
        case "two":
            return "Two";
        case 3.0:
            return "Three";
        default:
            return "Other";
    }
}

Осталось сделать функцию, которая запускает каждую из этих функций 100 000 раз и возвращает среднее время.

function benchmark($testFunction, $iterations, $inputs) {
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        foreach ($inputs as $input) {
            $testFunction($input);
        }
    }
    $end = microtime(true);
    return ($end - $start) * 1000;
}

$iterations = 100000;
$inputs = [1, "two", 3.0, "unknown"];

echo "Scenario 1 - if-else time: " . benchmark("test_numeric_if_else", $iterations, $inputs) . "ms\n";
echo "Scenario 1 - switch time: " . benchmark("test_numeric_switch", $iterations, $inputs) . "ms\n";

echo "Scenario 2 - if-else time: " . benchmark("test_string_if_else", $iterations, $inputs) . "ms\n";
echo "Scenario 2 - switch time: " . benchmark("test_string_switch", $iterations, $inputs) . "ms\n";

echo "Scenario 3 - if-else time: " . benchmark("test_mixed_if_else", $iterations, $inputs) . "ms\n";
echo "Scenario 3 - switch time: " . benchmark("test_mixed_switch", $iterations, $inputs) . "ms\n";

В итоге мы получим следующее

+-----------------+-----------+-----------+
| Test Scenario   | If-else   | Switch    |
+-----------------+-----------+-----------+
| Numeric values  | 25.487 ms | 38.195 ms |
+-----------------+-----------+-----------+
| String values   | 27.280 ms | 36.887 ms |
+-----------------+-----------+-----------+
| Mixed values    | 24.140 ms | 31.791 ms |
+-----------------+-----------+-----------+

Мы видим, что в каждом сценарии оператор if-else быстрее, чем switch, но разница между ними настолько мала, что мы можем даже не учитывать ее в реальном сценарии.

Если функции требуется 24 или 32 мс для завершения, мы, как люди, не почувствуем разницы.

Создание второй игровой площадки

Что касается итеративных блоков, то мы собираемся повторить тот же процесс, имея 3 разных сценария:

Сценарий 1: Сумма массива целых чисел

function test_for_loop($arr) {
    $sum = 0;
    for ($i = 0, $count = count($arr); $i < $count; $i++) {
        $sum += $arr[$i];
    }
    return $sum;
}

function test_while_loop($arr) {
    $sum = 0;
    $i = 0;
    $count = count($arr);
    while ($i < $count) {
        $sum += $arr[$i];
        $i++;
    }
    return $sum;
}

function test_foreach_loop($arr) {
    $sum = 0;
    foreach ($arr as $value) {
        $sum += $value;
    }
    return $sum;
}

Сценарий 2: Найти максимальное значение в массиве

function test_for_max($arr) {
    $max = $arr[0];
    for ($i = 1, $count = count($arr); $i < $count; $i++) {
        if ($arr[$i] > $max) {
            $max = $arr[$i];
        }
    }
    return $max;
}

function test_while_max($arr) {
    $max = $arr[0];
    $i = 1;
    $count = count($arr);
    while ($i < $count) {
        if ($arr[$i] > $max) {
            $max = $arr[$i];
        }
        $i++;
    }
    return $max;
}

function test_foreach_max($arr) {
    $max = $arr[0];
    foreach ($arr as $value) {
        if ($value > $max) {
            $max = $value;
        }
    }
    return $max;
}

Сценарий 3: Создать массив квадратов

function test_for_squares($count) {
    $squares = array();
    for ($i = 0; $i < $count; $i++) {
        $squares[] = $i * $i;
    }
    return $squares;
}

function test_while_squares($count) {
    $squares = array();
    $i = 0;
    while ($i < $count) {
        $squares[] = $i * $i;
        $i++;
    }
    return $squares;
}

function test_foreach_squares($count) {
    $squares = array();
    $range = range(0, $count - 1);
    foreach ($range as $value) {
        $squares[] = $value * $value;
    }
    return $squares;
}

Для повторных тестов мы будем использовать тот же подход, что и раньше:

function benchmark($testFunction, $iterations, $inputs) {
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        foreach ($inputs as $input) {
            $testFunction($input);
        }
    }
    $end = microtime(true);
    return ($end - $start) * 1000;
}

$iterations = 100000;
$inputs = [
    range(1, 10),
    range(1, 20),
    range(1, 30)
];

echo "Scenario 1 - for loop time: " . benchmark("test_for_loop", $iterations, $inputs) . "ms\n";
echo "Scenario 1 - while loop time: " . benchmark("test_while_loop", $iterations, $inputs) . "ms\n";
echo "Scenario 1 - foreach loop time: " . benchmark("test_foreach_loop", $iterations, $inputs) . "ms\n";

echo "Scenario 2 - for loop time: " . benchmark("test_for_max", $iterations, $inputs) . "ms\n";
echo "Scenario 2 - while loop time: " . benchmark("test_while_max", $iterations, $inputs) . "ms\n";
echo "Scenario 2 - foreach loop time: " . benchmark("test_foreach_max", $iterations, $inputs) . "ms\n";

$iterations = 100000;
$inputs = [
    10,
    20,
    30
];

echo "Scenario 3 - for loop time: " . benchmark("test_for_squares", $iterations, $inputs) . "ms\n";
echo "Scenario 3 - while loop time: " . benchmark("test_while_squares", $iterations, $inputs) . "ms\n";
echo "Scenario 3 - foreach loop time: " . benchmark("test_foreach_squares", $iterations, $inputs) . "ms\n";

Результаты получились более интересными, чем в предыдущем бенчмарке:

+------------------------+-----------+-----------+--------------+
| Test Scenario          | For loop  | While     | Foreach      |
+------------------------+-----------+-----------+--------------+
| Sum of array integers  | 164.025 ms | 164.323 ms | 126.592 ms |
+------------------------+-----------+-----------+--------------+
| Maximum value in array | 175.080 ms | 185.791 ms | 112.764 ms |
+------------------------+-----------+-----------+--------------+
| Create array of squares| 260.133 ms | 259.297 ms | 240.117 ms |
+------------------------+-----------+-----------+--------------+

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

Заключение

Обе таблицы дают четкое сравнение между различными конструкциями и их производительностью в конкретных тестовых сценариях.

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?

20.08.2023 18:21

Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2023-2024 годах? Или это полная лажа?".

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией

20.08.2023 17:46

В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.

Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox

19.08.2023 18:39

Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest

19.08.2023 17:22

В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!

Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️

18.08.2023 20:33

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL

14.08.2023 14:49

Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.