String hashCode performance

250
24 августа 2017, 19:47

Добрый день. Известно, что хэшкод для строк кэшируется в поле hash в момент первого вызова методы hashCode(), и для последующих вызовов не рассчитывается. Далее, есть такой код:

public static void main(String[] args) {
        int hashInt = 0;
        String one = "qwersdfdsfsfdstyuiop";
        long start = System.nanoTime();
        hashInt = one.hashCode();
        System.out.println(System.nanoTime() - start + " : " + hashInt);
        String two = "poiuytradadasda";
        start = System.nanoTime();
        hashInt = two.hashCode();
        System.out.println(System.nanoTime() - start + " : " + hashInt);
    }

результат:

125701 : -1509822895
766 : 1617334817

Откуда такая разница во времени? Спасибо за ответы.

Answer 1

JVM загружает и компилирует в нативный код используемые классы и методы по мере необходимости. При первом обращении к System.out, String.hashCode загружаются по цепочке используемые классы, методы компилируются в нативный код.

Если при запуске Java указать флаг -XX:+PrintCompilation, то можно увидеть какие методы компилируются на лету:

 70    1       3       java.lang.String::hashCode (55 bytes)
 71    3       3       java.lang.String::equals (81 bytes)
 72    2       3       java.lang.Object::<init> (1 bytes)
 72    7     n 0       java.lang.System::arraycopy (native)   (static)
 73    6       3       java.lang.String::indexOf (70 bytes)
 73    4       3       java.lang.String::<init> (82 bytes)
 74    5       3       java.util.Arrays::copyOfRange (63 bytes)
 74   11       3       java.lang.String::charAt (29 bytes)
 75    9       3       java.lang.CharacterData::of (120 bytes)
 75   10       3       java.lang.CharacterDataLatin1::getProperties (11 bytes)
 76   12       3       java.lang.String::length (6 bytes)
 76   13       3       java.lang.Character::toLowerCase (9 bytes)
 76    8       3       java.lang.Math::min (11 bytes)
 77   14       3       java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
 82   15       3       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
 84   16       3       java.lang.AbstractStringBuilder::append (29 bytes)
 85   17       3       java.lang.StringBuilder::append (8 bytes)
 85   18       3       java.io.WinNTFileSystem::isSlash (18 bytes)
 86   20       4       java.lang.String::charAt (29 bytes)
 86   19  s    3       java.lang.StringBuffer::append (13 bytes)
 87   11       3       java.lang.String::charAt (29 bytes)   made not entrant
 90   21       3       java.lang.StringBuilder::append (8 bytes)

Мораль здесь такова, что бенчмарки писать сложно ибо существует масса влияющих факторов, например:

  • Just-in-time компиляция;
  • Сборщик мусора;
  • Влияние других процессов.

Чтобы снизить влияние по крайней мере первого фактора, нужно до измерений запустить цикл «прогрева» в котором загрузятся и скомпилируются все используемые методы.

Например, если добавить цикл прогрева и отделить вывод, то можно получить уже более стабильные результаты:

class Test {
    private final String value;
    private int hash;
    private long time;
    public Test(String value) {
    this.value = value;
    }
    //измеряем hashcode
    public void measure() {
        long start = System.nanoTime();
        hash = value.hashCode();
        time = System.nanoTime()-start;        
    }
    //печатаем
    private void print() {  
        System.out.println(time + " : " + hash);
    }
    public static void main(String[] args) {
        warmup();
        runTests();
    }
    //прогрев
    private static void warmup() {
        for(int i=0; i<10; i++) {
            Test test = new Test(i+"");
            test.measure();
        }
    }
    //наши тесты
    private static void runTests() { 
        Test[] tests = new Test[] {
            new Test("qwersdfdsfsfdstyuiop"),
            new Test("poiuytradadasda")
        };
        for(Test test : tests) {
            test.measure();
        }
        for(Test test : tests) {
            test.print();
        }
    }
}

В этом коде разница уже будет не такой ощутимой, хотя еще осталась масса проблем: System.nanoTime неточен на одиночных вычислениях, не учитывается сборщик мусора и пул строк.

Подбробнее о проведении микроизмерений можно почитать в обсуждении в английском Stackoverflow: How do I write a correct micro-benchmark in Java?. Там же есть ссылки на популярные инструменты для данных целей.

READ ALSO
Обновление UI во время выполнения

Обновление UI во время выполнения

Что нужно дописать в код, чтобы текст обновлялся на кнопке раз в секунду, или ткните где искать инфу, заранее благодарю

294
Наполнение ExpandableLV SpannableString-элементами

Наполнение ExpandableLV SpannableString-элементами

Ломаю голову второй деньНеобходимо программно менять цвет у элементов ExpListView (Не родителей)

233
Как правильно удалить созданный Диалог?

Как правильно удалить созданный Диалог?

Создаю диалог со своей разметкой:

355
Как правильно загрузить веб-проект на github.com

Как правильно загрузить веб-проект на github.com

Добрый деньСоздал веб-проект с применением технологий (Tomcat, JDBC, JSP, Servlets, Maven etc)

327