Главная » Хабрахабр » [Перевод] Что не так с популярными статьями, рассказывающими что foo быстрее чем bar?

[Перевод] Что не так с популярными статьями, рассказывающими что foo быстрее чем bar?

Но вот подобная статья ("What performance tricks actually work") недавно собрала на Реддите относительно большой рейтинг и даже попала в PHP дайджест на Хабре. Примечание переводчика: я тоже думал, что время статей "Что быстрее — двойные или одинарные кавычки?" прошло еще 10 лет назад. Соответственно, я решил перевести статью с критическим разбором этих и подобных им "тестов".

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

Главная проблема

Но что важнее всего — подобные тесты бессмысленны и в то же время вредны. Такие тесты являются неверными по многим причинам, начиная с постановки вопроса и заканчивая ошибками реализации.

  • Бессмысленны потому, что никакой практической ценности не несут. Ни один реальный проект еще никогда не был ускорен с использованием методов, приводимых в таких статьях. Просто потому, что не различия в синтаксисе имеют значение для производительности, а обработка данных.
  • Вредны потому, что они приводят к появлению дичайших суеверий и — что еще хуже — побуждают ничего не подозревающих читателей писать плохой код, думая при этом что "оптимизируют" его.

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

Одинарные против двойных

Разумеется, никакие кавычки не быстрее. Взять пресловутые кавычки, "одинарные против двойных". При этом PHP код сохраняется в формате opcode, где одинаковые строковые литералы сохраняются как абсолютно идентичные сущности, безотносительно к тому, какие кавычки были использованы в РНР скрипте. Во-первых, есть такая вещь, как opcode cache, которая сохраняет результат парсинга РНР скрипта в кэш-памяти. Что означает отсутствие даже теоретической разницы в производительности.

Это означает, что любые полученные результаты продемонстрируют лишь проблемы в тестовом окружении. Но даже если мы не будем использовать opcode cache (хотя должны, если наша задача — реальное увеличение производительности), мы обнаружим, что разница в коде парсинга настолько мала (несколько условных переходов, сравнивающих однобайтные символы, буквально несколько инструкций процессора) что она будет абсолютно необнаружима. Тем не менее, почти каждый месяц появляется энергичный тестировщик чтобы раскрыть обществу воображаемую "разницу" в производительности. Есть очень подробная статья, Disproving the Single Quotes Performance Myth от core-разработчика PHP Никиты Попова, которая детально разбирает этот вопрос.

Логические нестыковки

Вы это серьезно? Часть тестов вообще бессмысленна, просто с точки зрения постановки вопроса: Например, тест озаглавленный "Действительно ли throw — супер-затратная операция?" это по сути вопрос "Действительно ли, что обрабатывать ошибку будет более затратно, чем не обрабатывать?". Но это же не значит, что новую функциональность не нужно добавлять вообще, под столь смехотворным предлогом. Разумеется, добавление в код какой-либо принципиальной функциональности сделает его "медленнее". Программа должна быть полезной и работать без ошибок в первую очередь. Если так рассуждать, то самая быстрая программа — та, которая не делает вообще ничего! Но если сама постановка вопроса не имеет смысла — то зачем вообще тогда тестировать производительность? И только после того как это достигнуто, и только в случае, если она работает медленно, ее нужно оптимизировать. Забавно, что тестировщику не удалось корректно реализовать даже этот бессмысленный тест, что будет показано в следующим разделе.

WTF? Или вот другой пример, тест озаглавленный "Действительно ли $row[id] будет медленнее, чем $row['id']?" это по сути вопрос "Какой код быстрее — тот который работает с ошибками, или без?" (поскольку писать id без кавычек в данном случае — это ошибка уровня E_NOTICE, и такое написание будет объявлено устаревшим в будущих версиях РНР). Ошибка должна быть исправлена просто потому, что это ошибка, а не потому что заставит код работать медленнее. Какой смысл вообще измерять производительность кода с ошибками? Забавно, что тестировщику не удалось корректно реализовать даже этот бессмысленный тест, что будет показано в следующим разделе.

Качество тестов

Но, как правило, подобные тесты делаются левой пяткой, и в итоге полученные результаты оказываются бессмысленными и не соответствующими поставленной задаче. И снова — даже заведомо бесполезный тест должен быть последовательным, непротиворечивым — то есть измерять сравнимые величины.

Но в актуальном тесте он измерял не только try catch, но и throw, выбрасывая исключение при каждой итерации цикла. Например, наш бестолковый тестировщик взялся измерять "избыточное использование оператора try..catch". Но такой тест просто некорректен, поскольку в реальной жизни ошибки возникают не при каждом исполнении скрипта.

И если тестировщик берется сравнивать "скорость парсинга json и xml", то он не должен использовать в тестах экспериментальную функцию. Разумеется, тесты не должны производиться на бета-версиях РНР и не должны сравнивать мейнстримные решения с экспериментальными.

О подобном примере из недавно опубликованной статьи уже говорилось выше: автор теста попытался узнать, действительно ли код, вызывающий ошибку ("Use of undefined constant") будет медленнее, чем код без ошибок (в котором используется синтаксически корректный строковый литерал), но не справился даже с этим заведомо бессмысленным тестом, сравнивая производительность взятого в кавычки числа с производительностью числа, написанного без кавычек. Некоторые тесты попросту демонстрируют полное непонимание тестировщиком поставленной им же самим задачи. Разумеется, писать числа без кавычек в РНР можно (в отличие от строк), и в итоге автор тестировал совершенно другую функциональность, получив неверные результаты.

Существуют расширения РНР, такие как XDebug, которые могут оказывать очень большое влияние на результаты тестов. Есть и другие вопросы, которые необходимо принимать во внимание, такие как тестовое окружение. Или уже упоминавшийся opcode cache, который должен быть обязательно включен при тестах производительности, чтобы результаты тестов могли иметь хоть какой-то смысл.

Поскольку РНР процесс умирает целиком после каждого запроса, то имеет смысл тестировать производительность всего жизненного цикла, начиная с создания соединения с веб-сервером и заканчивая закрытием этого соединения. То, как производится тестирование, также имеет значение. Есть утилиты, такие как Apache benchmark или Siege, которые позволяют это делать.

Реальное улучшение производительности

Что тесты производительности бесполезны по определению? Все это хорошо, но какой вывод должен сделать читатель из данной статьи? Но что действительно важно — это причина, по которой они должны запускаться. Разумеется, нет. Всегда должна быть конкретная причина для запуска тестов производительности. Тестирование на пустом месте — это пустая трата времени. Когда ваше приложение начинает работать медленно, вы должны заняться профилированием, что означает замер скорости работы различных участков кода, чтобы найти самый медленный. И эта причина называется "профилирование". Чаще всего это или гораздо больший, чем требуется, объем обрабатываемых данных, или запрос к внешнему источнику данных. После того как такой участок найден, мы должны определить причину. Для первого случая оптимизация будет заключаться в уменьшении количества обрабатываемых данных, а для второго — в кэшировании результатов запроса.

Что действительно важно — это количество данных, которые мы передаем на обработку. К примеру, с точки зрения производительности нет никакой разницы, используем ли мы явно прописанный цикл, или встроенную функцию РНР для обработки массивов (которая по сути является лишь синтаксическим сахаром). Это даст нам громадный прирост производительности, который будет реальным. В случае, если оно необоснованно велико, мы должны урезать его, или переместить обработку куда-то еще (в базу данных). В то время как разница между способами вызова цикла для обработки данных вряд ли будет заметна вообще.

Но опять же, такие тесты не должны делаться на пустом месте. Только после выполнения таких обязательных улучшений производительности, или в случае если мы не можем урезать количество обрабатываемых данных, мы можем приступить к тестам производительности. Для того чтобы начать сравнивать производительность явного цикла и встроенной функции, мы должны быть четко уверены, что именно цикл является причиной проблемы, а не его содержимое (спойлер: разумеется, это содержимое).

Сам запрос выполняется достаточно быстро, но Doctrine требуется довольно много времени, чтобы переварить несколько тысяч параметров. Недавний пример из моей практики: в коде был запрос с использованием Doctrine Query Builder, который должен был принимать несколько тысяч параметров. В итоге запрос был переписан на чистый SQL, а параметры передавались в метод execute() библиотеки PDO, который справляется с таким количеством параметров практически мгновенно.

Разумеется, нет. Значит ли это, что я больше никгда не буду использовать Doctrine Query Builder? И только в исключительных случаях стоит использовать менее удобный, но более производительный способ. Он идеально подходит для 99% задач, и я продолжу использовать его для всех запросов.

Если бы у меня возникла дурацкая идея заняться тем, каким образом вызывается цикл, то я попросту потерял бы время без какого-либо положительного результата. Запрос и параметры для этой выборки конструировались в цикле. А не тот код, который считался медленным давным-давно, в далекой-далекой галактике, или код, который кому-то пришло в голову назвать медленным на основании бессмысленных тестов. И в этом-то заключается самая что ни на есть суть всех оптимизаций производительности: оптимизировать только тот код, который работает медленно в вашем конкретном случае.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Пишем торговых роботов с помощью графического фреймворка StockSharp. Часть 1

Один из них – бесплатная платформа StockSharp, которую можно использовать для профессиональной разработки торговых терминалов и торговых роботов на языке C#. В нашем блоге мы много пишем о технологиях и полезных инструментах, связанных с биржевой торговлей. API, с целью создания ...

[Перевод] Сверхинтеллект: идея, не дающая покоя умным людям

Расшифровка выступления на конференции Web Camp Zagreb Мачея Цегловского, американского веб-разработчика, предпринимателя, докладчика и социального критика польского происхождения. В 1945 году, когда американские физики готовились к испытанию атомной бомбы, кому-то пришло в голову спросить, не может ли такое испытание зажечь ...