Комментарии 23
По аналогии «Эффективное использование С++».
Такая книга будет устаревать довольно быстро. А полезные советы превращаться во вредные.
Ещё в 9% случаев нужно копать в сторону эффективности используемых фреймворков и тулкитов.
Оставшийся 1% бизнесу, как правило, не интересен.
Конечно, всё зависит от задач. Но если брать среднюю температуру по больнице, предположение выше не так уж далеко от истины.
Видимо, принцип «скомпилируй, посмотри сгенерированный код» до сих пор актуален.
И ещё мини вывод: знание ассемблера нужно.
Видимо, принцип «скомпилируй, посмотри сгенерированный код» до сих пор актуален.
И ещё мини вывод: знание ассемблера нужно.
Это в полной мере справедливо для C++, т.к. AOT-компилятор. С Java всё намного сложнее, т.к. в JIT-компиляторе огромное количество оптимизаций, а их применение зависит от собранного профиля. Последнее означает, что результат JIT-компиляции может отличаться не только между кодом реального приложения и бенчмарка, так и в разные запуски приложения, да даже в разные моменты времени в одном запущенном приложении.
Пример:
В некотором коде может произойти
NullPointerException
, но в течение сбора профиля оно ни разу не случалось, поэтому JIT-компилятор C2 скомпилировал только так называемый common case — код, который исполняется часто, а остальную часть кода (обработка NPE
в данном случае) не скомпилировал вовсе, оставив так называемую uncommon trap. Если в какой-то момент возникнет NullPointerException
, то HotSpot вынужден будет откатиться в режим интерпретатора для этого участка кода, а в результате получить деоптимизацию всего метода. Когда JIT-компилятор вновь решится компилировать код и как он это сделает — предсказать мне невозможно.И еще одно — лобовая реализация описанного в ассемблере скорее всего будет эффективнее
В конкретном примере из статьи (и во многих других) — да. Но не стоит недооценивать умение JIT-компилятора учитывать собираемый профиль (= учитывать реальное исполнение кода, реальные данные в нашем приложении).
90% случаев достаточно использовать подходящие структуры данных и алгоритмы.
И писать оптимальные SQL селекты. Если там несколько join-ов, которые выдают 50 тысяч строчек по 100 столбцов, а потом данные фильтруются Java-кодом, то тут даже ассемблер не поможет.
Впрочем, всё это не отменяет того факта, что
в 90% случаев достаточно использовать подходящие структуры данных и алгоритмы.
b = a*a;
c = b*b;
res = c*c;
Или можно воспользоваться бинарным разложением степени (Алгоритмические трюки для программистов). Пример выше — частное решение.
Возможно есть ещё способ использовать биты в экспоненте, в случае степени кратной 2.
У меня по этому поводу что то смутное в голове есть.
Java умеет в JNI, а вызываемый модуль можно и на асме написать, главное, что бы выигрыш был больше, чем накладные расходы на вызов этой функции.
float/double
, FPU прекрасно посчитает x^y
как 2^(y*log(x))
(при условии, что x > 0
). Правда, FYL2X
(y*log(x)
) раз в 20-40 дороже умножения/деления в зависимости от архитектуры. Но выигрыш будет заметен на больших степенях.Java умеет в JNI, а вызываемый модуль можно и на асме написать, главное, что бы выигрыш был больше, чем накладные расходы на вызов этой функции.
Насколько дорого ходить в JNI — можно прикинуть, если сравнить бенчмарки с интринсиком и без (последний бенчмарк).
Странно, что jit не умеет в такую оптимизацию. Те же gcc и clang умеют проводить подобного рода оптимизации. Кто-нибудь может подсказать, почему это всё ещё не реализовано в java jit компиляторе?
В какую именно? Заменять a * a * ... * a
, как это сделано в примере? Так это не совсем законно (нарушает лево-ассоциативность операции умножения).
А есть аналог --ffast-math?
Нет. В Java вычисления с плавающей точкой реализованы в соответствии со стандартом IEEE 754. Это явно указано в javadoc к java.lang.StrictMath
.
Напротив, --ffast-math
допускает использование оптимизаций, нарушающих указанный стандарт.
Поэтому я и спросил про наличие такого флага :) понял, спасибо. Правильно ли я понимаю, что jit может проводить оптимизации над int?
Попробовал ещё в SIMD (независимые вычисления, простые циклы) — тоже безрезультатно.
Вывод: либо JIT-компилятор в это не умеет, либо я неправильно его готовлю.
Если в двух словах, то jit в векторизацию умеет, но нестабильно и непредсказуемо.
Разбор перформансных задач с JBreak (часть 4)