Обновить
-2

Пользователь

Отправить сообщение

Так о том и речь, что копмилятор — это не волшебная коробка, которая сама во всем разберётся. Языковые конструкции и правила вроде приведенных в статье являются директивами JIT компилятору, чтобы он стал таким "умным" и гарантированно мог применять агрессивные оптимизации — в том числе inline-встраивание и преобразование final метода в статический вызов, — зная, что логика приложения после этого не нарушится.

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

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

В apache.commons-math есть Fraction и BigFraction, которые реализуют этот подход.

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

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

Вот бенчмарки Fraction в сравнении с BigDecimal:

@Benchmark
public void fractionCall(Blackhole blackhole) {

Fraction result = new Fraction(1.2)
.multiply(new Fraction(0.4))
.divide(new Fraction(0.8))
.multiply(new Fraction(0.6))
.add(new Fraction(1.25))
.divide(new Fraction(0.75));

blackhole.consume(result);
}


---

Benchmark Mode Cnt Score Error Units
bigDecimalCall thrpt 150 3825.518 ± 53.397 ops/ms
bigFractionCall thrpt 150 114.674 ± 2.151 ops/ms
fractionCall thrpt 150 1717.929 ± 10.849 ops/ms
bigDecimalCall avgt 150 ≈ 10⁻⁴ ms/op
bigFractionCall avgt 150 0.009 ± 0.001 ms/op
fractionCall avgt 150 0.001 ± 0.001 ms/op
bigDecimalCall ss 150 0.030 ± 0.023 ms/op
bigFractionCall ss 150 0.143 ± 0.088 ms/op
fractionCall ss 150 0.022 ± 0.003 ms/op

Или же уловка языковой модели с целью внушить такое мнение читателю)

Согласен с вашими доводами. Про странность совета погорячиться)

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

Нечто подобное было во времена мидлетов в JavaME для кнопочных телефонов — там поначалу плавающей точки не было вообще и приходилось самим изобретать велосипед.

В общем советы полезные, но по поводу хранения денежных значений в long — как минимум странный.

BigDecimal ведь нужен не "для красоты при выводе", это прежде всего для корректности операций над числами с плавающей точкой.
При использовании примитивов есть большая вероятность получить неожиданный результат, что весьма критично для денежных операций. Например, данное выражение будет ложным: 0.3 + 0.6 == 0.9, т.к. сумма будет равна 0.8999999999999999. Связано это не с конкретным языком программирования, а с особенностью представления чисел с плавающей точкой в двоичной системе счисления, где некоторые числа просто невозможно точно представить. BigDecimal как раз и призван решать эту проблему.

Для методов - вряд ли. Если брать прсто компилятор, то final методы он компилирует точно в такую же инструкцию invokevirtual, что и обычные методы. Это сделано для обратной совместимости с клиентским кодом, используюшим методоы, которые вдруг были перекомпилированы с удалением или добавлением final. Здесь в конце статьи описано подробнее: https://blogs.oracle.com/javamagazine/post/mastering-the-mechanics-of-java-method-invocation
Что касается JIT компилятора, то у него есть механизмы оптимизации байткода и он все равно производит глобальный анализ дерева классов при загрузке. Хотя, возможно, final и является директивой для JIT в каких-то реализациях JVM, но и то только только на этапе анализа класса при загрузке. Но в любом случае к vtable это отношения не имеет.
Так что касательно методов и классов final несет больше семантический смысл, чем прирост производительности.

Зато final полезно при объявлении статических полей класса. Т.к. в этом случае значения, указанные в static final поля при компиляции напрямую инлайнятся в стек операндов тех методов, которые его используют (например вызывается инструкция iconst_0 для интового поля), без предварительной инициализации класса. В противном случае, обращение к static полю без final компилируется в инструкцию getstatic, которая уже производит инициализацию класса перед обращением к полю, что, соответственно, занимает время и память.

Есть один замечательный канал по электронике для начинающих:

https://www.youtube.com/@HiDev

Там товарищ очень доходчиво объясняет принципы работы электронных компонентов и устройств.

Вот, в частности, видос про полупроводники:

https://www.youtube.com/watch?v=OMGdSCaMVD0&list=PL1s3wneoR_-on-07THWG5GFEZ-_mm-Pd2

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность