Комментарии 72
Ну, не знаток PHP, но многое, по понятным причинам, можно перенести на другие языки, и я полностью согласен, за исключением throw.
Автор пишет правильно, что обработанная ошибка лучше, чем необработанная, даже если надо пожертвовать миллисекундами, но в жизни есть ситуации, когда программист использует исключения не для обработки ошибок, а для flow control в штатной и очень часто используемой ситуации. И это может быть проблемой.
Да, даже без вопроса производительности это bad design, но многие так делают (я тоже делал), но понимание того, как такое решение влияет на код, КМК, важно
Да, я согласен. Сам такого не видел, но слышал, что иногда throw используется тупо как замена goto, и в итоге throw используется при штатном выполнении кода, а не только при возникновении ошибок. Но что характерно — здесь опять же, в первую очередь это bad design, а уже во вторую — производительность. То есть, плохой код надо исправлять потому что он плохой, а не потому что он на пару миллисекунд медленнее.
Первая мысль, которая возникает у разработчика — сделать попытку привести строку к числу и, если не проканает, то считать ввод не числом, а строкой и дальше думать как ее обработать.
С точки зрения кода, как правило, проще. Да и читается лучше. Но по факту сначала идет незримый парсинг, чтобы привести к числу, потом обработка исключения, а потом уже более интересный парсинг строки (и код даже становится чуть чище за счет того, что мы точно знаем, что это не число). Но при видимой чистоте кода то, что происходит «за кулисами» многие забывают.
Не то, чтобы это совсем проблема. Скажем честно — в подавляющем большинстве случаев горлышко бутылки на I/O операциях или на зависимостях на третьи компоненты типа БД, и, если это не так, то без профилирования заниматься оптимизацией плохая идея. Но понимать проблему, конечно, стоит.
Честно говоря, в РНР это делается простым условием. Если, скажем, мы ожидаем int, то
$int = filter_var($raw_input, FILTER_VALIDATE_INT);
if (is_int($int)) {
// число, производим операции над $int
} else {
// в $raw_input строка, парсим её
}
или я неверно понял задачу
Это правильный дизайн (проверка, а потом разбираться). Но (не уверен в PHP, но в Джаве часто видел), что сначала тупо пытаются привести тип к численному, а уж если свалится, то работаем со строкой.
Ну да, логично. Поскольку в пхп типизации вообще, по сути, никакой, то можно приводить что угодно к чему угодно, и это не будет ошибкой.
Насколько я знаю, в java "из коробки" нет функции проверки строки на предмет конвертируемости в int/double вроде "is_int(x)". Альтернатив немного — тянуть Apache Commons или использовать регулярки. А с регулярками можно поймать числа вроде "0x061", "1,602176565E-19". Плюс в разных локалях разный символ для отделения целой части от дробной.
Думаю что из за всех этих сложностей, на дизайн забивают и обрабатывают исключение при попытке преобразования.
1) Проверка
Если (тут есть грабли): обойти грабли
2) Исключение
Если (наступили на грабли): кричим об этом
Только не кричим, а отходим назад и делаем вид, что грабель не было.
В обоих случаях мы выполняем примерно одну и ту же работу: надо пробежаться по строке и определить, является ли каждый символ составным элементом числа.
Отдельный метод в джаве будет выглядеть ооооооооочень странным, поскольку сделает столько же работы, сколько и полноценный парсинг, но пользы принесет меньше. Я, отчасти, понимаю, зачем это нужно в php, ввиду очень своеобразной системы типов, но в языках с более строгой системой типов это будет просто мусором в пространстве имен.
А уж использование регулярных выражений для проверки на число — это прям совсем смешно :)
Почему бы методу не возвращать, к примеру, Optional<Integer>
? И исключений не надо, и работы лишней не делается.
Потому что в php нет дженериков?
Можно еще вот так:
[$result, $error] = fn();
но это так, для тех кто хочет проникнуться Go
Я отвечал на вот это:
Отдельный метод в джаве будет выглядеть ооооооооочень странным, поскольку сделает столько же работы, сколько и полноценный парсинг, но пользы принесет меньше.
Я бы в этом случае больше предложил использовать что-то с тройным состоянием, вроде Mono из Project Reactor: оно может содержать значение, если все ок, может не содержать никакого значения, если был null, или же содержать исключение, если случилась ошибка.
Ну и опять же, лично мне нравится вариант go с явным разделением на «ошибки» и «исключения» (panic).
дальше основной проблемой становится невозможность в php описать ни maybe/optional ни туплы аля го. И остаются только исключения. Которые становятся проблемой если у вас control flow на них завязан.
Я говорил про null на входе :) Optional только на выходе выдается.
Ситуация примерно такая: Optional<Integer> isInteger(String s)
В случае ошибки парсинга возвращаем Optional.empty()
, что будет если s = null
? И что делать если в моем случае null — вполне себе допустимое значение, и я хочу получить назад null? Если кидать NullPointerException
, то это как бы ломает всю идею использования Optional
.
В джаве так-то тоже есть Exception
, который не должен являться фатальным, и Error
, который даже не имеет смысла ловить.
К примеру, что должен этот метод вернуть, если ему передали null string?
А в чем, собственно, проблема? null string нельзя распарсить в число, значит возвращаем Optional.empty()
Результат работы функции можно вернуть через вызов исключения или как целое число с номером ошибки.
Мне кажется, это зависит от предназначения функции. Если это проверка, которая и должна вернуть 0 или 1 в зависимостиот переданного параметра, то исключение не нужно.
А если функция должна как-то обработать и вернуть введенное значение, то исключение — это как раз идеальный способ сообщить об ошибке. Но это будет именно ошибка, а не результат работы.
Ну, широко распространены две крайности:
- чуть что-то нестандартное — бросаем исключение
- во всех случаях возвращаем специальные значения, хотя на практике часто их обработка сводится к $result >0 || die, хотя их там под сотню разных может быть.
Как по мне, то надо как-то баланс соблюдать обычно, ориентируясь на планируемые типовые сценарии использования, что скорее всего будет делать клиент нашего кода при ошибке того или иного типа. Исключения именно для исключительных ситуаций, когда ожидаемое поведение клиента — корректно освободить ресурсы и умереть, выдав пользователю "что-то пошло не так" и записав трейсы в логи. А бросать с десяток исключений по цепочке только для того, чтобы вывести пользователю "этот логин занят, попробуйте другой", как-то не комильфо.
Ну вот кстати я бы посмотрел на реальный проект, который был ускорен таким способом.
Причем именно на проект целиком, а не на специально написанный цикл на 100500 итераций :)
Да и я у себя делал замеры, тупо через styleci менял по всему проекту туда-сюда и отличия вполне ощутимые, если это делать на регулярной основе (ну т.е. везде). Лучше всего эффект воспроизводится при множестве мелких вызовов.
Ну вот опять же, "множество" — это сколько? Может, лучше уменьшить это множество, чем оптимизировать его? Сколько должно быть этих мелких вызовов, чтобы разница стала хотя бы на пределе чувствительности радара ощущаться?
P.S. Я тут нагуглил этот самый иссью (а не PR): github.com/symfony/symfony/issues/21020 Но если в среднем «по больнице», то должно выходить ~6-10% (рабочий проект на Symfony + phpcbf). Но это так…
P.P.S. А с другой стороны не пофигу ли? Тут реальные плюсики по скорости «на шару», подключаешь фиксер и вуаля, а мы ещё сопротивляемся.
Ну опять же — никаким приростом уровня приложения тут и не пахнет. Я тут вижу стандартный вывод phpbench, тупо вызывающего по 10 тыщ раз одну и ту же функцию, не делающую ничего.
Все в точности, как говорится в статье выше — если только заставить эти несчастные подопытные функции выполнять хоть какую-то полезную работу, то все эти проценты улетучатся, как белых яблонь дым.
не делающую ничего.
а как еще вы можете померять оверхэд при вызове методов/функций?
Да, для вашего проекта и вашего кода возможно профита никакого. Для фреймворков — профит будет, как и для библиотек инфраструктурных. Банально потому что там все чуть посложнее может быть.
Опять же, я не вижу никакой проблемы поскольку такие вот "оптимизации" выполняются автоматически. Возможно в 8-ой версии пыха эта проблема с фолбэком в глобальный нэймспейс уже будет не актуальна.
Возможно в 8-ой версии пыха эта проблема с фолбэком в глобальный нэймспейс уже будет не актуальна.
А сфигали будет не актуальна? Заставим всех всегда явно прописывать неймспейс для всех функций? Ну это как-то… диковато.
namespace A;
if (some_foo()) { // \some_foo
function some_foo() { ... }
}
some_foo(); // \A\some_foo
Никак не буду мерить :)
В статье ясно сказано — оптимизировать надо то, что в тормозит в реальности, а не в чьём-то в воображении — и я с этим согласен.
На вызовы функций приходится 0.000...% общего времени исполнения кода. Ускорение этого участка на 5% я при всем желании не замечу.
Если моя программа работает медленно, то самое последнее, чем я буду заниматься — это пририсовывать палочки к вызовам функций.
На вызовы функций приходится 0.000...% общего времени исполнения кода.
Никак не буду мерить :)
ну как-то так наша дискуссия сейчас идет.
Во первых у вас возможно 0.0001% а у меня 10% (комбинаторы парсеров, очень простые функции, их много, и они оч много выполняются). Потому что бы сделать более честный бенчмарк и меряют оверхэд от фолбэка в глобальный неймспейс а не "реальное приложение", далее ваша задача экстраполировать результаты данного бенчмарка на свой код, и прикинуть будет профит или можно забить. У некоторых например за счет этого фолбэка на глобальный неймспейс работают подмены функций в тестах)
Во вторых, если эта "оптимизация" требует от меня лишь включить еще один фиксер в php-cs-fixer — я не вижу вообще никакой причины обсуждать "бесполезность" таких вещей. И нет, "пририсовывать палочки" не обязательно — можно сделать явные use function
, причем все это не руками а автоматически.
p.s. просто что бы вас как-то попугать. Если в системе будет эксплойт, и там будет возможность как-то подсунуть свой код на сервере — я легко могу сделать так что бы у вас в системе была подменена реализация функции password_hash
какая-нибудь. Но это так, страшилки и пугалки. Мы ж не на вордпрессах пишем.
Ну, я пока не видел приложения, которое показало бы стабильный измеряемый и подтверждаемый прирост производительности в 0.6% (6 процентов от 10 процентов) от добавления слешей перед вызовами функций. Когда увижу — тогда соглашусь — да, эта оптимизация стоит моего внимания.
До тех пор я буду по-старинке — сначала профилировать, потом оптимизировать. А не наоборот.
Проблема «мерять на реальном приложении» в том, что реальных приложений много разных. А померять стоимость вызова пустого метода двумя способами легко и просто, а дальше уже разработчик «реального приложения» может банально прикинуть актуальна ему эта проблема или нет (если у него за запрос всего пара сотен вызовов функций — ему не надо париться, а если у него пара миллионов вызовов на операцию — заморочаться можно).
Понятнее объясняю причины для такого способа измерений?
Доктрина, симфони. У меня были проекты где для бизнес логики нужно было много много вызовов функций простых делать (там рекурсивно дерево рефералов обходилось + чуть-чуть математики простой) и там такие вот "микро" оптимизации могли дать еще +10%. Были так же проекты где надо было класстеризовать точки, но это все единичные случаи конечно. А вот на инфраструктурных вещах профит может быть солидный.
Ну вот опять это лукавство :)
10% не от общего времени исполнения кода, а от времени, которое затрачивается на вызов функций. А сколько его там затрачивается?
10% не от общего времени исполнения кода, а от времени, которое затрачивается на вызов функцийВы додумали.
Вы когда говорите что «не нужно все это», вы уточняйте что помимо мизерного эффекта на реальный код, нужно еще мизерное количество усилий для того что бы эту оптимизацию выполнять.
Я не говорю что все всегда должны эту оптимизацию делать — это не так. Но обвинять в микрооптимизациях тех, кто просто в php-cs-fixer поставил опцию в том что они тратят свое время на что-то бесполезное я бы не стал.
Извините, но я не верю.
Даже в отчаянных попытках натянуть сову на глобус, в бесстыдно синтетических тестах без какой-либо полезной нагрузки вообще, по приведенным выше ссылкам получается 8.55% в прыжке.
Любая полезная нагрузка эту цифру только уменьшит, причем катастрофически.
То есть ваши 10% на приложение — это либо ошибка, либо вы одновременно оптимизировали что-то ещё — ту самую полезную нагрузку. Что является куда более реальным сценарием.
В любом случае, я бы хотел увидеть тесты.
Понятно, что такими оптимизациями заниматься надо не тогда, когда, поступают жалобы на тормоза, а именно когда выбирается стайл гайд.
Ну написано же, что даже не на пару тактов :)
В опкод кэше все строки равны. Идентичны. Эквивалентны. Нету там пары тактов.
Аргумент этот какой угодно, но не объективный. Учитывая живучесть мифа — так даже и вредный.
На объективный тянет другой аргумент, который приводили ниже — тупо удобство работы со спецсимволами.
А удобство это как раз субъективный, кому-то удобно, а кому-то нет.
Ну так эти пара тактов размазываются на сотни тысяч вызовов кэшированного кода.
Разницу между "50\$" vs '50$' я бы не назвал субъективной, но если вам так хочется, то могу снять это предложение, оно не имеет для меня ни малейшей ценности. Вопрос не в том, какой ещё аргумент посчитать объективным, а в том, что "пара тактов" таковым не является.
ЗЫ. Я бы не был настолько тошнотворно-серьезен, если бы этот миф не был настолько живуч.
Статья прикольная, человек заморочился ради развлечения, получил какие-то результаты — чего плохого-то? Никто же на полном серьезе не собирается следовать этим гайдлайнам. А статья — так ведь нельзя же делать только серьезные вещи и только с серьезным видом, иногда можно и посчитать количество ангелов на игольном конце.
Разумеется, никакие кавычки не быстреек вопросу о скорости:
и не лень же некоторым shift зажимать :)
В PHPStorm константные строки в двойных кавычках подсвечиваются и предлагается заменить кавычки на одинарные.

Я всегда, не задумываясь, соглашался за ради единообразия кода (ну в самом деле, почему нет?), потом задумался — вроде серьезный инструмент, не могут же его создатели быть жертвой этого заблуждения. Решил внимательно прочитать предупреждение и невольно улыбнулся :)

Разумеется, никакие кавычки не быстрее.
Зависит от языка и версии, кое-где (Perl) кое-когда (давно) разница была. Более того — ' '.$x.' ' было быстрее, чем " $x ". В PHP особо не сталкивался, там может быть привнесённое или в 2018м могут не помнить как оно было раньше.
Разумеется, тесты не должны производиться на бета-версиях РНР и не должны сравнивать мейнстримные решения с экспериментальными. И если тестировщик берется сравнивать «скорость парсинга json и xml», то он не должен использовать в тестах экспериментальную функцию.
Но где там используется экспериментальная функция? xmlrpc_encode
/ xmlrpc_decode
доступны с времен PHP 4, а json_encode
/ json_decode
— с PHP 5
Что не так с популярными статьями, рассказывающими что foo быстрее чем bar?