Pull to refresh

Comments 72

Ну, не знаток PHP, но многое, по понятным причинам, можно перенести на другие языки, и я полностью согласен, за исключением throw.
Автор пишет правильно, что обработанная ошибка лучше, чем необработанная, даже если надо пожертвовать миллисекундами, но в жизни есть ситуации, когда программист использует исключения не для обработки ошибок, а для flow control в штатной и очень часто используемой ситуации. И это может быть проблемой.
Да, даже без вопроса производительности это bad design, но многие так делают (я тоже делал), но понимание того, как такое решение влияет на код, КМК, важно

Да, я согласен. Сам такого не видел, но слышал, что иногда throw используется тупо как замена goto, и в итоге throw используется при штатном выполнении кода, а не только при возникновении ошибок. Но что характерно — здесь опять же, в первую очередь это bad design, а уже во вторую — производительность. То есть, плохой код надо исправлять потому что он плохой, а не потому что он на пару миллисекунд медленнее.

Мне не жалко — могу привести пример (достаточно часто повторяющийся и, чего греха таить, использующийся постоянно): есть какой-то user input, который штатно может принимать как числа, так и какую-то строку определенного формата.
Первая мысль, которая возникает у разработчика — сделать попытку привести строку к числу и, если не проканает, то считать ввод не числом, а строкой и дальше думать как ее обработать.
С точки зрения кода, как правило, проще. Да и читается лучше. Но по факту сначала идет незримый парсинг, чтобы привести к числу, потом обработка исключения, а потом уже более интересный парсинг строки (и код даже становится чуть чище за счет того, что мы точно знаем, что это не число). Но при видимой чистоте кода то, что происходит «за кулисами» многие забывают.

Не то, чтобы это совсем проблема. Скажем честно — в подавляющем большинстве случаев горлышко бутылки на 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

Я отвечал на вот это:


Отдельный метод в джаве будет выглядеть ооооооооочень странным, поскольку сделает столько же работы, сколько и полноценный парсинг, но пользы принесет меньше.
В «джаве» вы обязаны объявить все исключения которые вы «прокидываете» выше и не обрабатываете. Потому там ситуация чуть-чуть отличается от ситуации в php.

Ну и «отдельный метод» — понятия не имею о чем вы.
null — не всегда показатель ошибки, зачастую это вполне себе first-class value. К примеру, что должен этот метод вернуть, если ему передали null string?
Я бы в этом случае больше предложил использовать что-то с тройным состоянием, вроде Mono из Project Reactor: оно может содержать значение, если все ок, может не содержать никакого значения, если был null, или же содержать исключение, если случилась ошибка.
вам не предлагают null, вам предлагают `Optional`, это чуть-чуть другое, и в целом это дело позволяет вам определить вариант проверить были ли ошибки или нет.

Ну и опять же, лично мне нравится вариант 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()

Почему нельзя? Можно.


String s = null;
Object o = s;
Integer i = (Integer) o;
Это называется не «распарсить»

Может и называется, вопрос в контракте метода.

UFO just landed and posted this here

Беда вашего комментария в том, что вы прочитали из моего примера только одну строчку, а надо было — весь пример целиком ;-)


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

Могу привести другой пример.
Результат работы функции можно вернуть через вызов исключения или как целое число с номером ошибки.
UFO just landed and posted this here

Мне кажется, это зависит от предназначения функции. Если это проверка, которая и должна вернуть 0 или 1 в зависимостиот переданного параметра, то исключение не нужно.
А если функция должна как-то обработать и вернуть введенное значение, то исключение — это как раз идеальный способ сообщить об ошибке. Но это будет именно ошибка, а не результат работы.

Ну, широко распространены две крайности:


  • чуть что-то нестандартное — бросаем исключение
  • во всех случаях возвращаем специальные значения, хотя на практике часто их обработка сводится к $result >0 || die, хотя их там под сотню разных может быть.

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

В питоне так даже и принято. Например итераторы сигнализируют о конце итерации при помощи исключения.
Ну что ж. Холивар на тему "foo()" vs "\foo()" объявляется открытым! =)

Ну вот кстати я бы посмотрел на реальный проект, который был ускорен таким способом.
Причем именно на проект целиком, а не на специально написанный цикл на 100500 итераций :)

Кусок Symfony, 146%. Если погуглить, то можно найти где-то PR с апом производительности. Вроде как Fesor в одном из дайджестов скидывал эту ссылку.

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

Ну вот опять же, "множество" — это сколько? Может, лучше уменьшить это множество, чем оптимизировать его? Сколько должно быть этих мелких вызовов, чтобы разница стала хотя бы на пределе чувствительности радара ощущаться?

Ну иногда множество уменьшить нельзя.

P.S. Я тут нагуглил этот самый иссью (а не PR): github.com/symfony/symfony/issues/21020 Но если в среднем «по больнице», то должно выходить ~6-10% (рабочий проект на Symfony + phpcbf). Но это так…

P.P.S. А с другой стороны не пофигу ли? Тут реальные плюсики по скорости «на шару», подключаешь фиксер и вуаля, а мы ещё сопротивляемся.

Ну опять же — никаким приростом уровня приложения тут и не пахнет. Я тут вижу стандартный вывод phpbench, тупо вызывающего по 10 тыщ раз одну и ту же функцию, не делающую ничего.


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

не делающую ничего.

а как еще вы можете померять оверхэд при вызове методов/функций?


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


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

Возможно в 8-ой версии пыха эта проблема с фолбэком в глобальный нэймспейс уже будет не актуальна.


А сфигали будет не актуальна? Заставим всех всегда явно прописывать неймспейс для всех функций? Ну это как-то… диковато.
ну вопервых такие обсуждения были, а во вторых — я больше про всякие JIT и префетчинги, которые могут это дело учитывать что бы невилировать проблемы с производительностью.
JIT не поможет, функция может быть объявлена позже последующий вызов должен уже измениться:
namespace A;

if (some_foo()) { // \some_foo
    function some_foo() { ... }
}

some_foo(); // \A\some_foo
Ну как бы JIT как раз поможет, поскольку он может использовать информацию доступную в рантайме (кто-то объявил такую-то функцию) для оптимизаций. Ну это все так, пустые разговоры)

Никак не буду мерить :)
В статье ясно сказано — оптимизировать надо то, что в тормозит в реальности, а не в чьём-то в воображении — и я с этим согласен.
На вызовы функций приходится 0.000...% общего времени исполнения кода. Ускорение этого участка на 5% я при всем желании не замечу.


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

На вызовы функций приходится 0.000...% общего времени исполнения кода.

Никак не буду мерить :)

ну как-то так наша дискуссия сейчас идет.


Во первых у вас возможно 0.0001% а у меня 10% (комбинаторы парсеров, очень простые функции, их много, и они оч много выполняются). Потому что бы сделать более честный бенчмарк и меряют оверхэд от фолбэка в глобальный неймспейс а не "реальное приложение", далее ваша задача экстраполировать результаты данного бенчмарка на свой код, и прикинуть будет профит или можно забить. У некоторых например за счет этого фолбэка на глобальный неймспейс работают подмены функций в тестах)


Во вторых, если эта "оптимизация" требует от меня лишь включить еще один фиксер в php-cs-fixer — я не вижу вообще никакой причины обсуждать "бесполезность" таких вещей. И нет, "пририсовывать палочки" не обязательно — можно сделать явные use function, причем все это не руками а автоматически.


p.s. просто что бы вас как-то попугать. Если в системе будет эксплойт, и там будет возможность как-то подсунуть свой код на сервере — я легко могу сделать так что бы у вас в системе была подменена реализация функции password_hash какая-нибудь. Но это так, страшилки и пугалки. Мы ж не на вордпрессах пишем.

Ну, я пока не видел приложения, которое показало бы стабильный измеряемый и подтверждаемый прирост производительности в 0.6% (6 процентов от 10 процентов) от добавления слешей перед вызовами функций. Когда увижу — тогда соглашусь — да, эта оптимизация стоит моего внимания.


До тех пор я буду по-старинке — сначала профилировать, потом оптимизировать. А не наоборот.

UFO just landed and posted this here
еще раз — на «реальном» приложении все будет зависеть от количества вызовов методов для операций. У подавляющего большинство нет таких проблем и там будет копеешный выйгрыш, а если вы делаете какой-нибудь парсер SQL-я или там graphql-я то там выйгрыш может быть существенный (за счет большого количества вызовов).

Проблема «мерять на реальном приложении» в том, что реальных приложений много разных. А померять стоимость вызова пустого метода двумя способами легко и просто, а дальше уже разработчик «реального приложения» может банально прикинуть актуальна ему эта проблема или нет (если у него за запрос всего пара сотен вызовов функций — ему не надо париться, а если у него пара миллионов вызовов на операцию — заморочаться можно).

Понятнее объясняю причины для такого способа измерений?
UFO just landed and posted this here

Доктрина, симфони. У меня были проекты где для бизнес логики нужно было много много вызовов функций простых делать (там рекурсивно дерево рефералов обходилось + чуть-чуть математики простой) и там такие вот "микро" оптимизации могли дать еще +10%. Были так же проекты где надо было класстеризовать точки, но это все единичные случаи конечно. А вот на инфраструктурных вещах профит может быть солидный.

Ну вот опять это лукавство :)
10% не от общего времени исполнения кода, а от времени, которое затрачивается на вызов функций. А сколько его там затрачивается?

10% не от общего времени исполнения кода, а от времени, которое затрачивается на вызов функций
Вы додумали.
еще раз — у меня есть маленький пет проджект — SQL парсер на комбинаторах парсеров (игрался с ними), и там доля вызовов функций в целом значительная. Еще у меня был пет проджект — re2php, типа компиляция регулярных выражений в php код (что бы за линейное время разбирать жирные строчки ну и просто прошариться в NFA->DFA). И там вызовов функций было много, очень много (всякие `ord` и т.д.). И там оверхэд от вызова метода чувствуется и можно выжать дополнительно 5-10% (банальный пример — попробуйте реализовать mb_strlen на чистом php и померяйте)) Зачем это делать? Потому что есть специфические задачи когда надо парсить много и «юникод» там в определенных местах, а в большинстве мест хочется все делать за O(1).

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

Я не говорю что все всегда должны эту оптимизацию делать — это не так. Но обвинять в микрооптимизациях тех, кто просто в php-cs-fixer поставил опцию в том что они тратят свое время на что-то бесполезное я бы не стал.

Извините, но я не верю.


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


Любая полезная нагрузка эту цифру только уменьшит, причем катастрофически.


То есть ваши 10% на приложение — это либо ошибка, либо вы одновременно оптимизировали что-то ещё — ту самую полезную нагрузку. Что является куда более реальным сценарием.


В любом случае, я бы хотел увидеть тесты.

У нас замена `array_key_exists()` на `isset()` дала заметный (процентов 30) прирост производительности на проекте (там был парсинг json, деталей не помню). Главное — не забывать, что isset работает не так, как array_key_exists.
Я считаю, что микрооптимизации имеют право на жизнь во время выработки стайл гайда компании или проекта, формального или неформального — не суть. Грубо, если двойные кавычки таки медленее (пускай на пару тактов даже), а остальные аргументы за и против попахивают вкусовщиной и в целом уровновешены (5 человек говорит, что им одни удобнее, а 5 что другие), то почему бы и не выбрать одинарные? Хоть какой-то объективный аргумент в их пользу есть.

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

Ну написано же, что даже не на пару тактов :)
В опкод кэше все строки равны. Идентичны. Эквивалентны. Нету там пары тактов.


Аргумент этот какой угодно, но не объективный. Учитывая живучесть мифа — так даже и вредный.
На объективный тянет другой аргумент, который приводили ниже — тупо удобство работы со спецсимволами.

Есть пара тактов, только не в опкеше, а в парсере :)

А удобство это как раз субъективный, кому-то удобно, а кому-то нет.

Ну так эти пара тактов размазываются на сотни тысяч вызовов кэшированного кода.


Разницу между "50\$" vs '50$' я бы не назвал субъективной, но если вам так хочется, то могу снять это предложение, оно не имеет для меня ни малейшей ценности. Вопрос не в том, какой ещё аргумент посчитать объективным, а в том, что "пара тактов" таковым не является.


ЗЫ. Я бы не был настолько тошнотворно-серьезен, если бы этот миф не был настолько живуч.

Не факт, кстати, что размазывается. Опкеша может вообще не оказаться в рантайме (например, его нет в официальном докер-образе PHP), или он может быть отключен (в CLI например).
Нам почему-то кажется, что ТС слишком сильно напрягся по поводу этой статьи.
Статья прикольная, человек заморочился ради развлечения, получил какие-то результаты — чего плохого-то? Никто же на полном серьезе не собирается следовать этим гайдлайнам. А статья — так ведь нельзя же делать только серьезные вещи и только с серьезным видом, иногда можно и посчитать количество ангелов на игольном конце.

Так в том-то и дело что он даже и это посчитать не может :)

Разумеется, никакие кавычки не быстрее
к вопросу о скорости:
и не лень же некоторым shift зажимать :)

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

У меня такая логика в организации кода: одинарные кавычки обязательно использую для строк без управляющих последовательностей и переменных. Мне так потом легче читать свой код — различать т.с. «простые» строки от «непростых», особенно в случае когда нет подсветки синтаксиса.
Немного не по теме статьи — небольшое наблюдение про двойные и одинарные кавычки.

В PHPStorm константные строки в двойных кавычках подсвечиваются и предлагается заменить кавычки на одинарные.


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

Вроде это сторонний плагин типа EA Inspections или как-то так генерит, а не сам шторм.
UFO just landed and posted this here
1. Раньше была подстановка, а потом удалили
2. Строка в кавычках скопирована из стороннего источника (кода)
3. Просто работа со старым (своим/чужим) кодом, в котором использовался другой стиль
и т.д.
Разумеется, никакие кавычки не быстрее.

Зависит от языка и версии, кое-где (Perl) кое-когда (давно) разница была. Более того — ' '.$x.' ' было быстрее, чем " $x ". В PHP особо не сталкивался, там может быть привнесённое или в 2018м могут не помнить как оно было раньше.

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

Но где там используется экспериментальная функция? xmlrpc_encode / xmlrpc_decode доступны с времен PHP 4, а json_encode / json_decode — с PHP 5

Sign up to leave a comment.

Articles