Подтвердить что ты не робот

Почему String.equals() быстрее, чем он сам?

Я пытался создать более быструю версию метода String.equals() и начал его просто копировать. Результат, который я нашел, был довольно запутанным. Когда я запустил копию вставной версии, приурочил ее и сравнил ее с JVM, версия JVM была быстрее. Разница колебалась от 6x до 34x быстрее! Проще говоря, чем длиннее строка, тем больше разница.

    boolean equals(final char a[], final char b[]) {
    int n = a.length;
    int i = 0;

    while (n-- != 0) {
        if (a[i] != b[i]) return false;
        i++;
    }
    return true;
}

public static void main() throws Exception {
    String a = "blah balh balh";
    String b = "blah balh balb";

    long me = 0, jvm = 0;

    Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);

    final char lhs[] = (char[]) value.get(a);
    final char rhs[] = (char[]) value.get(b);
    for (int i = 0; i < 100; i++) {
        long t = System.nanoTime();
        equals(lhs, rhs);
        t = System.nanoTime() - t;
        me += t;
    }

    for (int i = 0; i < 100; i++) {
        long t = System.nanoTime();
        a.equals(b);
        t = System.nanoTime() - t;
        jvm += t;
    }

    System.out.println("me  = " + me);
    System.out.println("jvm = " + jvm);
}

Вывод:

me  = 258931
jvm = 14991

Метод equals(), который я написал, представляет собой скопированную версию версии, найденной в методе String.equals(). Почему версия JVM быстрее, чем версия с копированием. Разве это не то же самое?

Может ли кто-нибудь объяснить, почему я вижу такие видимые различия?

PS: Если вы хотите увидеть большие различия, вы можете создать длинные (действительно, очень длинные) строки с одним символом, отличающимся в конце.

4b9b3361

Ответ 1

Почему версия JVM быстрее, чем версия с копированием. Разве это не то же самое?

Удивительно, но это не так.

Сравнение строк - это такая вездесущая операция, что почти наверняка ваш компилятор JIT имеет встроенный String.equals(). Это означает, что компилятор знает, как создать специально созданный машинный код для сравнения строк. Это делается прозрачно для вас, программист, когда вы используете String.equals().

Это объясняет, почему String.equals() намного быстрее, чем ваш метод, даже если внешне они кажутся идентичными.

Быстрый поиск находит несколько отчетов об ошибках, в которых упоминается такое внутреннее свойство HotSpot. Например, 7041100: загрузка в исполняемом файле String.equals выполняется до нулевой проверки.

Соответствующий источник HotSpot можно найти здесь. Эти функции:

  848 Node* LibraryCallKit::make_string_method_node(int opcode, Node* str1, Node* cnt1, Node* str2, Node* cnt2) {

и

  943 bool LibraryCallKit::inline_string_equals() {

Ответ 2

Hotspot позволяет разработчикам предоставлять встроенную реализацию метода в дополнение к реализации Java. Код Java выгружается во время выполнения и заменяется оптимизированной версией. Это называется внутренним. Несколько сотен методов из базовых классов оптимизируются по внутренним возможностям.

Изучив исходный код OpenJDK, вы можете увидеть реализацию xring_equals x86_64. Вы также можете посмотреть vmSymbols, чтобы получить список всех instrinsics (поиск do_intrinsic)

Ответ 3

Как вы знаете, JAVA действительно не является языком, основанным на компиляторе или интерпретаторе. Под этим я подразумеваю, что Java использует оба, т.е. компилятор, для преобразования исходного кода в промежуточную форму, которая затем интерпретируется во время выполнения.

Он отмечает LoC (строку кода), выполненную, чтобы знать, какая часть кода выполняется чаще. Как только JAVA обнаруживает, что часть кода работает с превышением заданного порогового значения, он помечает его как горячий и отправляет этот кусок кода на лету компилятору для более оптимизированной версии, которая будет запущена при следующем запросе. Это называется JIT (как раз вовремя)

Теперь, поскольку оба кода в точности совпадают, JAVA HotSpot должен был оптимизировать оба метода одинаково и, следовательно, с одинаковым временем выполнения. К сожалению, это не так.

Как только JIT обнаруживает, что метод equals() горячий и вызывается слишком часто, он требует специальной оптимизации на аппаратном уровне.
Да, Intel разработала совершенно новый набор инструкций для ускорения обработки текста. Это означает, что есть отдельная инструкция, чтобы сделать метод Strings.equals() более быстрым, чем ваш метод equals, вставленный при копировании.

Теперь вопрос в том, как это происходит. Что ж, это просто: всякий раз, когда String.equals() теплый (т.е. используется чаще, но не интенсивно), компилятор выполняет ту же оптимизацию, что и метод вставки копии. Но когда метод equal() становится горячим, JIT напрямую использует новый набор команд для сравнения строк и, следовательно, быстрого выполнения.