«1. Введение
В этой статье мы узнаем, что такое встроенные функции и как они работают в Java и других языках на основе JVM.
2. Что такое внутренние компоненты?
Встроенная функция — это функция, которая обрабатывается компилятором или интерпретатором для нашего языка программирования. Точнее, это особый случай, когда компилятор или интерпретатор может заменить функцию альтернативной реализацией по разным причинам.
Язык программирования обычно обрабатывает это, понимая, что конкретный вызов метода является особенным, и всякий раз, когда мы вызываем этот метод, результирующее поведение отличается. Это позволяет нашему коду не отличаться от обычного, но реализация языка программирования может вмешиваться в особых случаях, чтобы дать дополнительные преимущества.
То, как это работает, зависит от языка программирования, а также от операционной системы и оборудования. Однако, поскольку они обрабатываются для нас, нам обычно не нужно знать какие-либо из этих деталей.
Внутренности могут дать различные преимущества. Замена определенных алгоритмов нативным кодом может повысить их производительность или даже использовать специфические функции операционной системы или базовое оборудование.
3. Встроенные функции в JVM
Виртуальная машина JVM реализует встроенные функции путем замены вызова точного метода для определенного класса альтернативной версией. JVM обрабатывает это сама, поэтому она будет работать только для основных классов и конкретных архитектур. Это также позволяет заменять только определенные методы, а не целые классы.
То, как это работает, зависит от JVM. Сюда входят не только разные версии JVM — например, Java 8 и Java 11. Это также включает в себя различные цели JVM — например, Linux против Windows — и особенно поставщиков JVM — Oracle против IBM. В некоторых случаях на них могут влиять определенные флаги командной строки, переданные JVM.
Это разнообразие означает, что невозможно определить, основываясь только на приложении, какие методы будут заменены встроенными, а какие нет. Это будет отличаться в зависимости от JVM, на котором запущено приложение. Но в некоторых случаях это может привести к удивительным результатам, в том числе к значительному повышению производительности, достигаемому просто за счет изменения используемой JVM.
4. Преимущества в производительности
Внутренние элементы часто используются для реализации более эффективной версии того же кода, например, путем использования деталей реализации работающей ОС или ЦП. Иногда это связано с тем, что он может использовать более эффективную реализацию, а иногда может зайти так далеко, что использует аппаратно-зависимую функциональность.
Например, HotSpot JDK имеет встроенную реализацию многих методов в java.lang.Math. В зависимости от конкретной JVM они потенциально могут быть реализованы с использованием инструкций ЦП для выполнения необходимых вычислений.
Простой тест продемонстрирует это. Например, возьмем java.lang.Math.sqrt(). Мы можем написать тест:
for (int a = 0; a < 100000; ++a) {
double result = Math.sqrt(a);
}
Этот тест выполняет операцию извлечения квадратного корня 100 000 раз, что занимает примерно 123 мс. Однако, если мы заменим этот код копией реализации Math.sqrt():
double result = StrictMath.sqrt(a);
Этот код делает то же самое, но вместо этого выполняется за 166 мс. Это увеличение на 35% за счет копирования реализации вместо того, чтобы позволять JVM заменять ее встроенной версией.
5. Невозможные реализации
В других случаях встроенные функции используются для ситуаций, когда код не может быть реализован на Java. Обычно они зарезервированы для случаев очень низкого уровня.
Например, рассмотрим метод onSpinWait() в классе java.lang.Thread. Этот метод указывает, что этот поток в настоящее время не выполняет никакой работы и что процессорное время может быть предоставлено другому потоку. Чтобы реализовать это, он должен работать на самом низком уровне.
«HotSpot JDK для архитектур x86 реализует это непосредственно на ЦП, используя код операции PAUSE. Единственным другим способом добиться этого было бы использование вызова JNI к собственному коду, и связанные с этим накладные расходы в первую очередь сводили бы на нет преимущества вызова.
6. Идентификация встроенных методов в Java
К сожалению, нет гарантированного способа определить методы, которые могут быть заменены встроенными версиями. Это связано с тем, что разные JVM или даже одна и та же JVM на разных платформах будут делать это для разных методов.
Однако при использовании Hotspot JVM начиная с Java 9 аннотация @HotSpotIntrinsicCandidate используется для всех методов, которые могут быть заменены. Добавление этой аннотации не приводит к автоматической замене метода. На самом деле это происходит в базовой JVM. Вместо этого разработчики JVM знают, что эти методы особенные, и с ними нужно быть осторожными.
Другие JVM могут обрабатывать это иначе, если они вообще идентифицируются. Это включает JVM Hotspot в Java 8 или более ранней версии.
7. Резюме
Мы не можем писать наши программы, полагаясь на наличие встроенных функций, потому что нет никакого способа узнать, будут ли они доступны или нет на JVM времени выполнения. Однако они представляют собой убедительный подход, который JVM может использовать для улучшения работы программ.
Эти встроенные функции могут быть добавлены — и часто добавляются — в более новые версии JVM. Таким образом, это позволяет улучшить наш уже работающий код, просто обновив JVM, на которой мы работаем, так что это еще одна причина убедиться, что мы всегда в курсе наших зависимостей и среды выполнения.