Пул строк: "Te" + " st "быстрее, чем"Test"?
Я пытаюсь провести некоторый тест производительности в отношении пула строк. Однако исхода не предвидится.
Я сделал 3 статических метода
- метод perform0 ()... создает новый объект каждый раз
- метод perform1 ()... Строковый литерал "Test"
- метод perform2 ()... Строковое константное выражение " Te " + " st "
Мое ожидание было (1. самый быстрый -> 3. самый медленный)
- "тест" из-за объединения строк
- " Te " + " st " из-за объединения строк но немного медленнее, чем 1 из-за оператора +
- новая строка(..) из-за отсутствия объединения строк.
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 ответов:
"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?
Некоторые известные правила, которые вы нарушаете:
- Вы не отбрасываете результаты итераций "прогрева" вашего тестового ядра.
- у вас не включено ведение журнала 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
Я думаю, что это не относится к вашему делу. В первом тесте производительности вы всегда создаете новый объект, поэтому создается 1000000String("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.