Вопрос: действительно ли обработка исключений в Java медленная?
Традиционные знания, а также многие результаты Google говорят, что исключительная логика не должна использоваться для нормального выполнения программ в Java. Обычно приводятся две причины:
- это действительно медленно - даже на порядок медленнее, чем обычный код (причины могут быть разными),
а также
- это грязно, потому что люди ожидают, что в исключительном коде будут обрабатываться только ошибки.
Этот вопрос о № 1.
В качестве примера, эта страница описывает обработку исключений Java как «очень медленную» и связывает медлительность с созданием строки сообщения об исключении - «эта строка затем используется при создании объекта исключения, который генерируется. Это не быстро». В статье « Эффективная обработка исключений в Java» говорится, что «причина этого заключается в аспекте создания исключительных ситуаций, связанном с созданием объекта, который, таким образом, делает создание исключений по своей сути медленным». Другая причина в том, что генерация трассировки стека замедляет его.
Мое тестирование (с использованием Java 1.6.0_07, Java HotSpot 10.0, в 32-битной Linux) показало, что обработка исключений не медленнее, чем в обычном коде. Я попытался запустить метод в цикле, который выполняет некоторый код. В конце метода я использую логическое значение, чтобы указать, возвращать ли или бросать . Таким образом, фактическая обработка одинакова. Я пытался запустить методы в разных порядках и усреднять время тестирования, думая, что это могло быть разогревом JVM. Во всех моих тестах бросок был, по крайней мере, так же быстро, как и возврат, если не быстрее (до 3,1% быстрее). Я полностью открыт к возможности того, что мои тесты были неправильными, но я не видел ничего такого, как пример кода, сравнение тестов или результаты за последние год или два, которые показывают, что обработка исключений в Java действительно медленный.
На этом пути меня привел API, который мне был нужен, чтобы использовать исключения как часть обычной логики управления. Я хотел исправить их в их использовании, но теперь я не смогу. Должен ли я вместо этого похвалить их за их дальновидность?
В статье « Эффективная обработка исключений Java в процессе компиляции точно в срок» авторы предполагают, что одного только наличия обработчиков исключений, даже если исключений не выдается, достаточно, чтобы компилятор JIT не оптимизировал код должным образом, что замедляет его , Я еще не проверял эту теорию.
источник
exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow
- дает полное и подробное объяснение, почему. И он был парнем, который написал Java lib. Поэтому именно он определяет контракт API для классов. / согласен Билл К на этом.Ответы:
Это зависит от того, как реализованы исключения. Самый простой способ - использовать setjmp и longjmp. Это означает, что все регистры ЦПУ записываются в стек (что уже занимает некоторое время) и, возможно, необходимо создать некоторые другие данные ... все это уже происходит в операторе try. Оператор throw должен разматывать стек и восстанавливать значения всех регистров (и, возможно, других значений в виртуальной машине). Так что try и throw одинаково медленны, и это довольно медленно, однако, если исключение не выдается, выход из блока try в большинстве случаев вообще не занимает времени (так как все помещается в стек, который очищается автоматически, если метод существует).
Sun и другие признали, что это, возможно, неоптимально и, конечно, виртуальные машины становятся все быстрее и быстрее с течением времени. Есть еще один способ реализации исключений, который заставляет себя попробовать себя молниеносно (на самом деле вообще ничего не происходит для попытки - все, что должно произойти, уже сделано, когда класс загружается виртуальной машиной), и это делает бросок не столь медленным , Я не знаю, какая JVM использует эту новую, лучшую технику ...
... но вы пишете на Java, чтобы ваш код позже работал только на одной JVM в одной конкретной системе? Так как, если он когда-либо будет работать на любой другой платформе или любой другой версии JVM (возможно, любого другого производителя), кто сказал, что они также используют быструю реализацию? Быстрый более сложный, чем медленный, и не всегда возможен во всех системах. Вы хотите остаться портативным? Тогда не надейтесь на быстрые исключения.
Это также имеет большое значение для того, что вы делаете в блоке try. Если вы открываете блок try и никогда не вызываете какой-либо метод из этого блока try, блок try будет очень быстрым, поскольку JIT может фактически обработать бросок как простое goto. Ему не нужно ни сохранять состояние стека, ни разматывать стек, если выдается исключение (ему нужно только перейти к обработчикам перехвата). Тем не менее, это не то, что вы обычно делаете. Обычно вы открываете блок try и затем вызываете метод, который может вызвать исключение, верно? И даже если вы просто используете блок try в своем методе, какой это будет метод, который не вызывает никакой другой метод? Будет ли он просто рассчитать число? Тогда зачем вам исключения? Есть гораздо более элегантные способы регулирования потока программ. Для всего остального, кроме простой математики,
Смотрите следующий тестовый код:
Результат:
Замедление в блоке try слишком мало, чтобы исключить мешающие факторы, такие как фоновые процессы. Но блок захвата убил все и сделал это в 66 раз медленнее!
Как я уже сказал, результат не будет таким плохим, если вы поместите try / catch и throw все в один и тот же метод (method3), но это специальная оптимизация JIT, на которую я бы не рассчитывал. И даже при использовании этой оптимизации бросок все еще довольно медленный. Так что я не знаю, что вы пытаетесь сделать здесь, но определенно есть лучший способ сделать это, чем использовать try / catch / throw.
источник
nanoTime()
требует Java 1.5, и в моей системе была доступна только Java 1.4, которую я использовал для написания кода выше. Также это не играет огромной роли на практике. Единственное различие между ними состоит в том, что одна составляет наносекунду, а другая - миллисекунды, и на нееnanoTime
не влияют манипуляции с часами (которые не имеют значения, если вы или системный процесс не изменяете системные часы именно в тот момент, когда выполняется тестовый код). В целом вы правы, хотя,nanoTime
конечно, лучший выбор.try
блоком, но нетthrow
. Вашthrow
тест бросающее исключения 50% времени она проходит черезtry
. Это явно ситуация, когда неудача не является исключительной . Сокращение этого показателя до 10% приводит к значительному снижению производительности. Проблема с этим типом теста заключается в том, что он поощряет людей вообще отказываться от использования исключений. Использование исключений для исключительной обработки обращений работает намного лучше, чем показывает ваш тест.return
. Он оставляет метод где-то посередине тела, может быть, даже в середине операции (которая на данный момент завершена только на 50%), иcatch
блок может быть на 20 кадров стека вверх (у метода естьtry
блок, вызывающий method1, который вызывает method2, который вызывает mehtod3, ..., а в method20 в середине операции выдается исключение). Стек должен быть размотан на 20 кадров вверх, все незавершенные операции должны быть отменены (операции не должны выполняться наполовину), а регистры ЦП должны находиться в чистом состоянии. Это все требует времени.К вашему сведению, я продлил эксперимент, который провел Меки:
Первые 3 такие же, как у Меки (мой ноутбук явно медленнее).
method4 идентичен method3 за исключением того, что он создает,
new Integer(1)
а не делаетthrow new Exception()
.method5 похож на method3, за исключением того, что он создает,
new Exception()
не бросая его.method6 похож на method3, за исключением того, что он генерирует предварительно созданное исключение (переменную экземпляра), а не создает новое.
В Java большая часть затрат на создание исключения - это время, затрачиваемое на сбор трассировки стека, которое происходит при создании объекта исключения. Фактическая стоимость исключения, хотя и велика, значительно меньше, чем стоимость создания исключения.
источник
Алексей Шипилёв провел очень тщательный анализ, в котором он сравнил исключения Java при различных сочетаниях условий:
Он также сравнивает их с производительностью проверки кода ошибки на разных уровнях частоты ошибок.
Выводы (цитаты дословно из его поста) были:
Поистине исключительные исключения прекрасно исполнены. Если вы используете их по назначению и сообщаете о действительно исключительных случаях только среди подавляющего числа неисключительных случаев, обрабатываемых обычным кодом, использование исключений является выигрышем в производительности.
Затраты на производительность исключений имеют два основных компонента: построение трассировки стека, когда создается экземпляр Exception, и разматывание стека во время генерирования исключения.
Затраты на построение трассировки стека пропорциональны глубине стека в момент создания исключения. Это уже плохо, потому что кто на Земле знает глубину стека, на котором этот метод броска будет вызван? Даже если вы отключите генерацию трассировки стека и / или кэшируете исключения, вы сможете избавиться только от этой части затрат на производительность.
Затраты на разборку стека зависят от того, насколько нам повезло с приближением обработчика исключений в скомпилированном коде. Тщательная структуризация кода, чтобы избежать глубокого поиска обработчиков исключений, вероятно, помогает нам стать более удачливыми.
Если мы исключим оба эффекта, затраты на исключение производительности будут такими же, как в местном филиале. Независимо от того, насколько красиво это звучит, это не значит, что вы должны использовать исключения как обычный поток управления, потому что в этом случае вы зависите от оптимизации компилятора! Вы должны использовать их только в действительно исключительных случаях, когда частота исключений амортизирует возможные неудачные затраты на повышение действительного исключения.
Оптимистическое эмпирическое правило кажется 10 ^ -4 частоты для исключений достаточно исключительным. Это, конечно, зависит от тяжести самих исключений, точных действий, выполняемых в обработчиках исключений и т. Д.
В результате, когда исключение не выдается, вы не платите за него, поэтому, когда исключительное условие достаточно редкое, обработка исключения выполняется быстрее, чем при использовании
if
каждый раз. Полный пост очень стоит прочитать.источник
Мой ответ, к сожалению, слишком длинный, чтобы оставлять сообщения здесь. Итак, позвольте мне кратко изложить здесь и направить вас к http://www.fuwjax.com/how-slow-are-java-exceptions/ для получения мельчайших подробностей.
Настоящий вопрос здесь заключается не в том, «насколько медленно« сбои регистрируются как исключения »по сравнению с« кодом, который никогда не дает сбоев »?» поскольку принятый ответ мог бы заставить вас поверить. Вместо этого следует задать вопрос: «Насколько медленными являются« сообщения об ошибках как об исключениях »по сравнению с сообщениями о сбоях другими способами?» Как правило, два других способа сообщения о сбоях - либо с помощью дозорных значений, либо с помощью упаковщиков результатов.
Значения Sentinel - это попытка вернуть один класс в случае успеха и другой в случае неудачи. Вы можете думать об этом, как о возвращении исключения вместо того, чтобы его выбросить. Для этого требуется общий родительский класс с объектом успеха, а затем проверка «instanceof» и пара приведений для получения информации об успехе или ошибке.
Оказывается, что при угрозе безопасности типов значения Sentinel быстрее, чем исключения, но только примерно в 2 раза. Теперь это может показаться большим, но это в 2 раза покрывает только разницу в реализации. На практике этот коэффициент намного ниже, поскольку наши методы, которые могут потерпеть неудачу, гораздо интереснее, чем несколько арифметических операторов, как в примере кода в другой части этой страницы.
Обертки Result, с другой стороны, вообще не жертвуют безопасностью типов. Они объединяют информацию об успехах и неудачах в одном классе. Таким образом, вместо «instanceof» они предоставляют «isSuccess ()» и геттеры для объектов успеха и ошибок. Однако результирующие объекты примерно в 2 раза медленнее, чем с использованием исключений. Оказывается, что каждый раз создавать новый объект-обертку намного дороже, чем иногда создавать исключение.
Кроме того, исключения - это язык, предоставляющий способ указания на сбой метода. Нет другого способа узнать только из API, какие методы должны работать (в основном) всегда, а какие будут сообщать о сбое.
Исключения безопаснее, чем часовые, быстрее, чем объекты результата, и менее удивительны, чем оба. Я не предполагаю, что try / catch заменяет if / else, но исключения являются правильным способом сообщения о сбое, даже в бизнес-логике.
Тем не менее, я хотел бы отметить, что двумя наиболее частыми способами существенного влияния на производительность, с которыми я столкнулся, являются создание ненужных объектов и вложенных циклов. Если у вас есть выбор между созданием исключения или не созданием исключения, не создавайте исключение. Если у вас есть выбор между созданием исключения или созданием другого объекта все время, создайте исключение.
источник
Я расширил ответы, данные @Mecki и @incarnate , без заполнения трассировки стека для Java.
С Java 7+ мы можем использовать
Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)
. Но для Java6, смотрите мой ответ на этот вопросВыход с Java 1.6.0_45, на Core i7, 8 ГБ ОЗУ:
Таким образом, методы, возвращающие значения, работают быстрее, чем методы, генерирующие исключения. ИМХО, мы не можем спроектировать понятный API, просто используя типы возвращаемых данных как для потоков успеха, так и для ошибок. Методы, которые генерируют исключения без отслеживания стека, в 4-5 раз быстрее, чем обычные исключения.
Изменить: NoStackTraceThrowable.java Спасибо @Greg
источник
public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
With Java 7+, we can use
но позже написали,Output with Java 1.6.0_45,
что это результат Java 6 или 7?Throwable
конструктор сboolean writableStackTrace
аргументом arg. Но этого нет в Java 6 и ниже. Вот почему я дал пользовательскую реализацию для Java 6 и ниже. Таким образом, приведенный выше код предназначен для Java 6 и ниже. Пожалуйста, внимательно прочитайте первую строчку второго абзаца.Optional
или подобное пришло с запозданием на Java. До этого мы также использовали объекты-оболочки с флагами успеха / ошибки. Но это, кажется, немного взломано и не кажется мне естественным.Некоторое время назад я написал класс для проверки относительной производительности преобразования строк в целые с использованием двух подходов: (1) вызвать Integer.parseInt () и перехватить исключение, или (2) сопоставить строку с регулярным выражением и вызвать parseInt () только в случае успеха Я использовал регулярное выражение наиболее эффективным способом (т. Е. Создавал объекты Pattern и Matcher перед вмешательством в цикл), и я не печатал и не сохранял трассировки стека из исключений.
Для списка из десяти тысяч строк, если бы все они были действительными числами, подход parseInt () был в четыре раза быстрее, чем подход регулярных выражений. Но если бы только 80% строк были действительными, регулярное выражение было в два раза быстрее parseInt (). И если 20% были действительными, то есть исключение было сгенерировано и перехватывало 80% времени, регулярное выражение было примерно в двадцать раз быстрее, чем parseInt ().
Я был удивлен результатом, учитывая, что подход регулярных выражений обрабатывает допустимые строки дважды: один раз для соответствия и снова для parseInt (). Но бросать и ловить исключения более, чем восполнить это. Такая ситуация вряд ли случится очень часто в реальном мире, но если это произойдет, вам определенно не следует использовать технику ловли исключений. Но если вы проверяете только пользовательский ввод или что-то в этом роде, непременно используйте подход parseInt ().
источник
Integer.ParseInt()
), и я ожидаю, что в большинстве случаев пользовательский ввод будет правильным, поэтому для моего варианта использования кажется, что случайное попадание исключения - это путь ,Я думаю, что в первой статье упоминается об обходе стека вызовов и создании трассировки стека как о дорогой части, и хотя во второй статье это не сказано, я думаю, что это самая дорогая часть создания объекта. У Джона Роуза есть статья, в которой он описывает различные методы ускорения исключений . (Предварительное выделение и повторное использование исключения, исключений без трассировки стека и т. Д.)
Но все же - я думаю, что это следует рассматривать только как необходимое зло, в крайнем случае. Джон делает это, чтобы эмулировать функции на других языках, которые (пока) не доступны в JVM. Вы не должны привыкать использовать исключения для потока управления. Особенно не по причинам производительности! Как вы сами упомянули в # 2, вы рискуете замаскировать серьезные ошибки в своем коде, и это будет сложнее поддерживать для новых программистов.
Мне сказали, что микробенчмарки в Java на удивление трудно понять, особенно когда вы попадаете на территорию JIT, поэтому я действительно сомневаюсь, что использование исключений быстрее, чем «возврат» в реальной жизни. Например, я подозреваю, что в вашем тесте где-то между 2 и 5 стековыми фреймами? Теперь представьте, что ваш код будет вызываться компонентом JSF, развернутым JBoss. Теперь у вас может быть трассировка стека длиной в несколько страниц.
Возможно, вы могли бы опубликовать свой тестовый код?
источник
Не знаю, относятся ли эти темы, но я однажды хотел реализовать один трюк, основанный на трассировке стека текущего потока: я хотел узнать имя метода, который вызвал создание экземпляра внутри экземпляра класса (да, идея сумасшедшая, Я полностью бросил это). Таким образом , я обнаружил , что призвание
Thread.currentThread().getStackTrace()
является чрезвычайно медленно (из - за нативныйdumpThreads
метод , который он использует внутренне).Так что у Java
Throwable
, соответственно, есть нативный методfillInStackTrace
. Я думаю, что киллер-catch
блок, описанный ранее, как-то запускает выполнение этого метода.Но позвольте мне рассказать вам другую историю ...
В Scala некоторые функциональные возможности компилируются с использованием JVM
ControlThrowable
, что расширяетThrowable
и переопределяет егоfillInStackTrace
следующим образом:Поэтому я адаптировал тест выше (количество циклов уменьшилось на десять, моя машина немного медленнее :):
Итак, результаты:
Видите ли, единственная разница между
method3
и вmethod4
том, что они бросают различные виды исключений. Да,method4
все еще медленнее, чемmethod1
иmethod2
, но разница гораздо более приемлема.источник
Я провел некоторое тестирование производительности с JVM 1.5, и использование исключений было как минимум в 2 раза медленнее. В среднем: время выполнения тривиально небольшого метода более чем в три раза (3 раза) с исключениями. Тривиально маленький цикл, который должен был поймать исключение, увеличил время самовосстановления в 2 раза.
Я видел похожие цифры в производственном коде, а также в микро-тестах.
Исключения определенно НЕ должны использоваться для всего, что часто вызывается. Бросок тысяч исключений в секунду вызовет огромную горлышко бутылки.
Например, использование «Integer.ParseInt (...)» для поиска всех неверных значений в очень большом текстовом файле - очень плохая идея. (Я видел, как этот служебный метод убивает производительность в рабочем коде)
Использование исключения для сообщения о плохом значении в форме пользовательского интерфейса, вероятно, не так уж плохо с точки зрения производительности.
Будь это хорошая практика проектирования, я бы пошел с правилом: если ошибка нормальная / ожидаемая, то используйте возвращаемое значение. Если это ненормально, используйте исключение. Например: чтение пользовательских данных, плохие значения - это нормально - используйте код ошибки. Передав значение во внутреннюю служебную функцию, неверные значения должны быть отфильтрованы с помощью вызывающего кода - используйте исключение.
источник
Исключительная производительность в Java и C # оставляет желать лучшего.
Как программисты это заставляет нас жить по правилу «исключения должны вызываться нечасто», просто по практическим соображениям производительности.
Однако, как компьютерные ученые, мы должны восстать против этого проблемного состояния. Человек, создающий функцию, часто не знает, как часто она будет вызываться, или более вероятен успех или неудача. Только звонящий имеет эту информацию. Попытка избежать исключений приводит к неясным идентификаторам API, где в некоторых случаях у нас есть только чистые, но медленные версии исключений, а в других случаях у нас бывают быстрые, но неуклюжие ошибки возвращаемого значения, а в других случаях мы получаем оба варианта: , Разработчику библиотеки, возможно, придется написать и поддерживать две версии API, и вызывающая сторона должна решить, какую из двух версий использовать в каждой ситуации.
Это своего рода беспорядок. Если бы исключения имели лучшую производительность, мы могли бы избежать этих неуклюжих идиом и использовать исключения, так как они должны были использоваться ... как средство возврата структурированных ошибок.
Мне бы очень хотелось, чтобы механизмы исключений были реализованы с использованием методов, близких к возвращаемым значениям, чтобы мы могли иметь производительность, близкую к возвращаемым значениям ... поскольку это то, к чему мы возвращаемся в чувствительном к производительности коде.
Вот пример кода, который сравнивает производительность исключения с производительностью возвращаемого значения ошибки.
открытый класс TestIt {
}
И вот результаты:
Проверка и распространение возвращаемых значений действительно добавляет некоторую стоимость к вызову baseline-null, и эта стоимость пропорциональна глубине вызова. При глубине цепочки вызовов, равной 8, версия проверки возвращаемого значения ошибки была примерно на 27% медленнее базовой версии, которая не проверяла возвращаемые значения.
В отличие от этого, эффективность исключений зависит не от глубины вызова, а от частоты исключений. Однако деградация при увеличении частоты исключений гораздо более драматична. Только с частотой ошибок 25% код работал в 24 раза медленнее. При частоте ошибок 100% версия исключения почти в 100 раз медленнее.
Это говорит мне о том, что, возможно, мы делаем неправильные компромиссы в наших реализациях исключений. Исключения могут быть быстрее, либо избегая дорогостоящих прогулок, либо просто превращая их в поддерживаемую компилятором проверку возвращаемого значения. Пока они этого не сделают, мы застряли, избегая их, когда хотим, чтобы наш код работал быстро.
источник
HotSpot вполне способен удалить код исключения для сгенерированных системой исключений, если он встроен. Однако явно созданные исключения и те, которые в противном случае не были удалены, тратят много времени на создание трассировки стека. Переопределите,
fillInStackTrace
чтобы увидеть, как это может повлиять на производительность.источник
Даже если генерирование исключения не является медленным, все равно это плохая идея - генерировать исключения для нормального выполнения программы. Используется таким образом, это аналог GOTO ...
Я думаю, что это на самом деле не отвечает на вопрос, хотя. Я полагаю, что «общепринятая» мудрость медленного создания исключений была верна в более ранних версиях Java (<1.4). Создание исключения требует, чтобы виртуальная машина создала всю трассировку стека. С тех пор многое изменилось в ВМ, чтобы ускорить процесс, и это, вероятно, одна область, которая была улучшена.
источник
break
илиreturn
, а неgoto
.Просто сравните, скажем, Integer.parseInt со следующим методом, который просто возвращает значение по умолчанию в случае непарсируемых данных, а не генерирует исключение:
Пока вы применяете оба метода к «действительным» данным, они оба будут работать примерно с одинаковой скоростью (даже если Integer.parseInt обрабатывает более сложные данные). Но как только вы попытаетесь проанализировать недопустимые данные (например, проанализировать «abc» 1.000.000 раз), разница в производительности должна быть существенной.
источник
Отличный пост о выполнении исключений:
https://shipilev.net/blog/2014/exceptional-performance/
Создание экземпляров против повторного использования существующих, с трассировкой стека и без, и т. Д .:
В зависимости от глубины трассировки стека:
Для других деталей (включая ассемблер x64 от JIT) прочитайте оригинальное сообщение в блоге.
Это означает, что Hibernate / Spring / etc-EE-shit работают медленно из-за исключений (xD) и перезаписи потока управления приложения в сторону от исключений (замените его на
continure
/break
и возвращаяboolean
флаги, как в C из вызова метода), чтобы повысить производительность вашего приложения в 10-100 раз в зависимости от того, как часто вы их кидаете))источник
Я изменил ответ @Mecki выше, чтобы method1 возвращал логическое значение и проверку в вызывающем методе, поскольку вы не можете просто заменить Exception ничем. После двух запусков метод1 все еще был самым быстрым или быстрым, как метод2.
Вот снимок кода:
и результаты:
Выполнить 1
Run 2
источник
Исключение предназначено для обработки неожиданных условий во время выполнения только.
Использование исключения вместо простой проверки, которое может быть выполнено во время компиляции, задержит проверку до времени выполнения. Это, в свою очередь, снизит эффективность программы.
Вызов исключения вместо простой проверки if..else также сделает код сложным для написания и обслуживания.
источник
Мое мнение о скорости исключения против проверки данных программно.
У многих классов был конвертер строк в значения (сканер / парсер), уважаемые и известные библиотеки;)
обычно имеет форму
имя исключения - только пример, обычно не проверяется (время выполнения), поэтому объявление throws - только моя картинка
иногда существует вторая форма:
никогда не бросать
Когда вторая недоступна (или программист читает слишком мало документов и использует только первую), напишите такой код с регулярным выражением. Регулярные выражения - это круто, политкорректно и т. Д.
с этим кодом программисты не имеют стоимости исключений. НО ИМЕЕТ ВСЕГДА сравнимую очень ВЫСОКУЮ стоимость регулярных выражений с небольшой стоимостью исключения иногда.
Я использую почти всегда в таком контексте
не анализируя stacktrace и т. д., я считаю, что после ваших лекций довольно быстро.
Не бойся исключений
источник
Почему исключения должны быть медленнее, чем обычно?
Пока вы не печатаете трассировку стека в терминал, не сохраняете ее в файл или что-то подобное, блок catch не выполняет больше работы, чем другие кодовые блоки. Итак, я не могу себе представить, почему «throw new my_cool_error ()» должно быть таким медленным.
Хороший вопрос, и я с нетерпением жду дополнительной информации по этой теме!
источник