Константы, строки и ключи

Давайте тестанём не предмет скорости ещё какую-нибудь чепуху.

Вот, допустим, константы. Одно из применений констант, это обозначение «магических» чисел. Например:

class Compressor
{
    const GZIP = 1;
    const BZIP = 2;
    const RAR = 3;
    const HUERAR = 4;
 
    public function compress($str, $type)
    {
        // ...
    }
}

То есть у нас есть некое множество значений (типов сжатия) и для обозначения каждого мы придумываем от балды какое-то число. А, чтобы с числами не запутаться, придумываем поверх него константу. И используем:

$compressed = $compressor->compress($plain, Compressor::GZIP);

С незапамятных пор так ведётся. Ещё Страуструп молодым был.

Однако, иногда посещает мысль, что PHP, это не Си и со строками он работает намного веселее. И почему бы не написать крамольное:

$compressed = $compressor->compress($plain, 'gzip');


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

Это удобно и прекрасно, но закрадывается мысль — православно ли это? Не убьют ли строки нашу бедную производительность? Проверим.

Допустим, мы что-то парсим. Например, HTML или BB-коды или шаблонизатор в очередной раз изобретаем. Первым делом разбираем текст на токены и хотим видеть результат примерно в таком виде:

$tokensBB = [
    [
        'type': 'text',
        'value': 'I am text',
    ],
    [
        'type': 'IMG',
        'value': 'SRC="смишные_котеки.jpg"',
    ],
];

Формат понятный, но, о ужас: мало того, что тип указан строкой, а не числовой константой, так ещё и ассоциативные массивы используются. Конец и скорости и памяти.

Напишем тест: https://github.com/vasa-c/utils/blob/master/php/const-string/keys.php.

Создаём список из 10 000 токенов, а потом всех их перебираем по нескольку раз и в зависимости от типа каждого токена выполняем различные действия. В качестве обработки отдельного токена — простая арифметическая операция. Всего типов токенов — 5.

Замеряем, как скорость создания, так и скорость перебора, а также прирост памяти после создания списка.

Результат:

PHP: 5.5.1
Count: 10000
Create: 0.031732082366943
Mem: 2 825 972
Process: 0.2093300819397

Итак, на моём дохлом нетбуке перебор происходит со скоростью 500 тысяч токенов в секунду (на сервере — около 5 млн.). Но нам, как всегда, мало, поэтому займёмся жёсткой оптимизацией.

Оптимизированный вариант: https://github.com/vasa-c/utils/blob/master/php/const-string/numbers.php.

Каждый токен записываем теперь: [тип числом, значение]. И от ассоциативных ключей избавились и типы числом записываем. Даже именованные константы использовать не будем — только числа, только хардкор!

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

PHP: 5.5.1
Count: 10000
Create: 0.031095027923584
Mem: 3 105 960
Process: 0.19729590415955

А не летает. Вообще не летает.

Выводы

Выводы всё те же.

1. Не надо оптимизировать то, что не надо.

2. Если всё таки надо — не стоит строить свои оптимизации на непонятно откуда взятых предпосылках, предрассудках и где-то краем уха подслушанных слухов.

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

А что наворочали создатели похапе, питона и всего подобного у них внутри, можно узнать только по исходникам. И как с этим делать микрооптимизацию тоже. Оно вам сильно надо?

7 комментариев »

  • > Это удобно и прекрасно
    Пожалуй насчет удобства не соглашусь. Если сделана константа в классе то тогда любая православная IDE мне по CTRL+SPACE подскажет варианты типа для передачи. А если передается строка то сиди и думай чего там вообще можеть быть jpg, JPG или jpeg…

    dallone, 19.08.2013, 12:26

  • dallone, ладно, предлагаю сойтись на мнении, что в зависимости от ситуации может быть удобен свой вариант :)

    vasa_c, 19.08.2013, 12:44

  • Я использую константы, причем иногда вполне и строковые. Типа

    const GZIP = ‘gzip’;

    Т.к. ‘gzip’ это просто строка, а Compressor::GZIP это структурированная инфа понятная для авторефакторинга

    так же как если в метод передавать имя класса, то лучше сделать что-то вроде \Some\App\Class::getClassname() (php < 5.5, т.к. про \Some\App\Class::class я в курсе :) ) чем передать 'Some\App\Class'

    SunChaser, 19.08.2013, 13:01

  • SunChaser, ну это-то вообще ересь, похуже описанной :)
    Мало того, что строки, так поверх них ещё и константы.

    vasa_c, 21.08.2013, 15:41

  • Приходится вертеться, раз в PHP нет Enumeration и перегрузки операторов

    SunChaser, 22.08.2013, 11:31

  • Используйте константы, потому что иначе в методе compress() придётся делать дополнительные преобразования, чтобы переданные ‘gZip’ или ‘GZIP’ было тоже самое, что и ‘gzip’. А также, чтобы человеку, который заглянет или будет использовать ваш код, было сразу понятно, взглянув на класс, с какими архивами он может работать. Оптимизация нужна там, где она нужна, в данном случае — оно излишне и только вредит читабельности и понятливости кода.

    Denis, 13.09.2013, 10:50

  • Denis, ну здесь то я как раз и пытался сказать, что никакая оптимизация здесь не нужна.
    И против констант ничего нет. Только в константу можно положить человекопонятную строку:
    const GZIP = ‘gzip’;
    а не непонятное магическое число, только ради той же «оптимизации».

    vasa_c, 13.09.2013, 10:56

Leave a comment