Этот пост о историческом опыте в сочетании с недавно примененными методами оптимизации производительности. Несколько лет назад я ругался на конкретное приложение, где мне нужно было обнаружить недокументированное поведение, похороненное по-настоящему умной инженерной «техникой».
Это было типичное монолитное приложение Java EE, отвечающее за выставление счетов. Точный код лучше забыть, но я напоминаю, что разработчик нашел действительно умный способ управления потоком бизнес-процессов.
Часть потока была обычным бесконечным беспорядком «если-то-еще», но что делало вещи более «захватывающими», так это то, что некоторые случайные элементы этих проверок были погружены в пользовательскую механику обработки исключений java.lang.RuntimeException . Таким образом, вы можете иметь код, подобный следующему:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
if (invoice.overdue()) { if (invoice.getCustomer().isKeyCustomer()) throw new InvoiceOverdueException(InvoiceOverdueException.KEY_CUSTOMER); else doSomething(); } else { if (invoice.getDueAmount() > BIG_AMOUNT) if (invoice.getCustomer().isKeyCustomer()) //be silent else throw new InvoiceExceededException(invoice.getDueAmount()); } |
И не короткие и легко читаемые блоки, как выше, но тысячи строк кода, разбросанных по всему приложению.
Я думаю, вы могли бы согласиться со мной, что это чертовски хороший способ сделать себя незаменимым. Никто не поймет, почему приложение ведет себя так, как оно есть.
Я вспомнил этот опыт из-за недавней задачи оптимизации Plumbr . Я хотел бы сказать, что наш код не использовал исключения, как описывал предыдущий случай, но, к сожалению, это не совсем верно. Один конкретный метод все еще имел исключение RuntimeException, создаваемое и генерируемое во время обычного потока кода. Я обнаружил этого одинокого злодея только из-за аномалии производительности в этом конкретном модуле.
Обычно исключение выдается только при неожиданных проблемах. Поэтому мы не ожидаем, что исключения будут генерироваться тысячами в секунду на поток. Но, как я, вы можете обнаружить метод, который использует исключения для более вероятных событий.
Я нашел виновника только из-за того, что это был часто используемый блок кода в конкретном алгоритме обхода графа, поэтому было крайне важно выжать из него последние миллисекунды. Удаление обработки исключений немедленно сделало этот блок кода более чем в 100 раз быстрее.
Это может вызвать у вас интерес — почему медленная обработка исключений? Медленная часть связана с созданием исключения. Или — если быть более точным — любой подкласс java.lang.Throwable .
Если вы помните, все конструкторы вызывают вызов конструктора по умолчанию суперкласса через вызов super (). Если вы не укажете этот вызов самостоятельно, компилятор будет любезен добавить его в сам байт-код. В любом случае, просматривая исходный код java.lang.Throwable , вы видите ответ, глядя в лицо:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public Throwable() { fillInStackTrace(); } public synchronized Throwable fillInStackTrace() { if (stackTrace != null || backtrace != null /* Out of protocol state */ ) { fillInStackTrace( 0 ); stackTrace = UNASSIGNED_STACK; } return this ; } private native Throwable fillInStackTrace( int dummy); |
Поэтому каждый раз, когда вы создаете новый Throwable (), вы в конечном итоге заполняете всю трассировку стека с помощью собственного вызова. Если вы не уверены, что это медленно, выполните следующий микробенч jmh, чтобы убедиться, что создание исключений в сотни раз дороже, чем создание обычных объектов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@BenchmarkMode (Mode.AverageTime) @OutputTimeUnit (TimeUnit.NANOSECONDS) public class NewExceptionTest { @GenerateMicroBenchmark public Object baseline() { return new Object(); } @GenerateMicroBenchmark public Object exceptional() { return new RuntimeException(); } } |
1
2
3
|
Benchmark Mode Thr Cnt Sec Mean Mean error Units j.NewExceptionTest.baseline avgt 1 5 5 3.275 0.029 nsec/op j.NewExceptionTest.exceptional avgt 1 5 5 1329.266 8.675 nsec/op |
Чтобы подвести итог этому — исключения должны использоваться для того, что указывает имя — для исключительных ситуаций. Если вы начнете злоупотреблять этой концепцией, вы либо сделаете свой код нечитабельным, либо начнете страдать от проблем с производительностью. Как минимум, я гарантирую, что вы получите тонны негативной кармы от этого.