Пул строк: "Te" + " st "быстрее, чем"Test"?


Я пытаюсь провести некоторый тест производительности в отношении пула строк. Однако исхода не предвидится.

Я сделал 3 статических метода

  • метод perform0 ()... создает новый объект каждый раз
  • метод perform1 ()... Строковый литерал "Test"
  • метод perform2 ()... Строковое константное выражение " Te " + " st "

Мое ожидание было (1. самый быстрый -> 3. самый медленный)

  1. "тест" из-за объединения строк
  2. " Te " + " st " из-за объединения строк но немного медленнее, чем 1 из-за оператора +
  3. новая строка(..) из-за отсутствия объединения строк.
Но бенчмарк показывает, что "Te"+"st" слабее, чем "Test".
new String(): 141677000 ns 
"Test"      : 1148000 ns 
"Te"+"st"   : 1059000 ns

new String(): 141253000 ns
"Test"      : 1177000 ns
"Te"+"st"   : 1089000 ns

new String(): 142307000 ns
"Test"      : 1878000 ns
"Te"+"st"   : 1082000 ns

new String(): 142127000 ns
"Test"      : 1155000 ns
"Te"+"st"   : 1078000 ns
...

Вот код:

import java.util.concurrent.TimeUnit;


public class StringPoolPerformance {

    public static long perform0() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = new String("Test");
        }
        return System.nanoTime()-start;
    }

    public static long perform1() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = "Test";
        }
        return System.nanoTime()-start;
    }

    public static long perform2() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = "Te"+"st";
        }
        return System.nanoTime()-start;
    }

    public static void main(String[] args) {
        long time0=0, time1=0, time2=0;
        for (int i=0; i<100; i++) {
            // result
            time0 += perform0();
            time1 += perform1();
            time2 += perform2();
        }

        System.out.println("new String(): " +  time0 + " ns");
        System.out.println(""Test"      : " + time1 + " ns");
        System.out.println(""Te"+"st"   : " + time2 + " ns");
    }
}
Может ли кто-нибудь объяснить, почему "Te"+"st" работает быстрее, чем "Test"? JVM делает здесь некоторые оптимизации? Спасибо.
5 7

5 ответов:

"Te" + "st" является выражением постоянной времени компилятора, и поэтому будет вести себя во время выполнения не иначе , чем просто "Test". Любой удар по производительности будет при попытке скомпилировать его, а не при попытке запустить.

Это легко доказать, разобрав ваш скомпилированный эталонный класс с помощью javap -c StringPoolPerformance:

public static long perform1();
  Code:
...
   7:   ldc #3; //int 1000000
   9:   if_icmpge   21
   12:  ldc #5; //String Test
   14:  astore_3
   15:  iinc    2, 1
...

public static long perform2();
  Code:
...
   7:   ldc #3; //int 1000000
   9:   if_icmpge   21
   12:  ldc #5; //String Test
   14:  astore_3
   15:  iinc    2, 1
...

Байтовый код методов абсолютно идентичен! Это определено спецификацией языка Java , 15.18.1:

Объект String является новым создано (§12.5), если выражение не является выражением константы времени компиляции (§15.28).

Разница между эталонами, которую вы испытываете, вероятно, связана с типичной изменчивостью или потому, что ваш эталон не идеален. Смотрите этот вопрос: Как написать правильный микро-бенчмарк на Java?

Некоторые известные правила, которые вы нарушаете:

  1. Вы не отбрасываете результаты итераций "прогрева" вашего тестового ядра.
  2. у вас не включено ведение журнала GC (особенно актуально, когда perform1() всегда выполняется сразу после теста, который создает миллион объектов).

Возможно, включается JIT-компилятор, и третий выполняет машинный код. Возможно, конкатенация была перемещена за пределы петли. Возможно, конкатенация никогда не выполняется, потому что переменная никогда не читается. Возможно, разница заключается в шуме, и ваши три образца случайно указывают один и тот же путь.

Надежный бенчмаркинг Java, Часть 1: Проблемы объясняет множество способов, которыми бенчмаркинг Java может пойти не так.

Бенчмаркинг чрезвычайно сложен. Многие факторы, как очевидные, так и тонкие, могут повлиять на ваши результаты. Чтобы получить точные результаты, необходимо досконально изучить эти проблемы, возможно, используя систему бенчмаркинга, которая решает некоторые из них. Перейдите к разделу Part 2, чтобы узнать о такой надежной платформе бенчмаркинга Java.

Не ждите, что микро-бенчмарки кода Java расскажут вам что-нибудь полезное, пока вы не поймете конкретные подводные камни, которые представляет архитектура JVM, и не ожидайте, что даже лучшие микро-бенчмарки предсказывают производительность реального приложения.

Я не знаю, какова ваша цель, но обучение использованию хорошего профилировщика и использование его в вашем реальном приложении обычно покажет вам, действительно ли данная строка является источником неэффективности, и позволит вам измерить эффект изменения кода. Время, потраченное на изучение профилировщика, вероятно, лучше, чем на написание и отладку микро-тестов.

Прежде всего было бы неплохо знать, что:

Если вы соединяете строки снова и снова, скажем, в цикле, то вы знаете, что, поскольку они неизменны, новые строки продолжают генерироваться. Компилятор javac внутренне использует для этого StringBuffer - так, например, у вас есть

String itemList = "";
 itemList=itemList + items[i].description;

В петле.

Происходит следующее: внутри цикла генерируются два объекта. Один StringBuffer-

itemList=new StringBuffer().append(itemList).
      append(items[i].description).toString();

Другой - это Строка, которая назначается itemList через toString ().'

Источник: http://thought-bytes.blogspot.com/2007/03/java-string-performance.html

Я думаю, что это не относится к вашему делу. В первом тесте производительности вы всегда создаете новый объект, поэтому создается 1000000 String("Test") объектов. Во втором и третьем примерах создается только один объект, на который указывает множество ссылок. Как было сказано ранее: "Te"+"st" обрабатывается как константа времени компилятора и различия слишком малы, чтобы сказать, что это быстрее, чем"тест".

Извините, что публикую ответ, но я не могу поместить это в комментарий, чтобы показать, насколько ошибочен этот бенчмарк. На Linux я поменял порядок и получаю:

Порядок вызывающе важен.

new String()   : 123328907 ns
"Test"         : 1153035 ns
"Te"+"st"      : 5389377 ns
"a"+"b"+"c"+"d": 1256918 ns

Марк Питерс прав, две строковые константы будут соединены без всякой жалости.

Это происходит из-за времени копирования, необходимого для объединения строковых объектов в соответствии с их размером.

Теперь они компилируются в объекты StringBuffer/StringBuilder компилятором, вы можете увидеть это, декомпилировав a .файл класса.

Вы должны взглянуть на эти классы,но помните, что при отображении StringBuilder или StringBuffer в виде строки будет создан новый объект String.