Досліджуючи невеликі дебати з використанням "" + n
і Integer.toString(int)
для перетворення цілочисеного примітиву у рядок, я написав цей мікровивідний знак JMH:
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class IntStr {
protected int counter;
@GenerateMicroBenchmark
public String integerToString() {
return Integer.toString(this.counter++);
}
@GenerateMicroBenchmark
public String stringBuilder0() {
return new StringBuilder().append(this.counter++).toString();
}
@GenerateMicroBenchmark
public String stringBuilder1() {
return new StringBuilder().append("").append(this.counter++).toString();
}
@GenerateMicroBenchmark
public String stringBuilder2() {
return new StringBuilder().append("").append(Integer.toString(this.counter++)).toString();
}
@GenerateMicroBenchmark
public String stringFormat() {
return String.format("%d", this.counter++);
}
@Setup(Level.Iteration)
public void prepareIteration() {
this.counter = 0;
}
}
Я запустив його із типовими параметрами JMH з обома віртуальними машинами Java, які існують на моїй машині Linux (сучасний 64-розрядний Mageia 4, процесор Intel i7-3770, 32 ГБ оперативної пам'яті). Першим JVM був той, який постачався з Oracle JDK 8u5 64-bit:
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
За допомогою цієї JVM я отримав майже те, що очікував:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32317.048 698.703 ops/ms
b.IntStr.stringBuilder0 thrpt 20 28129.499 421.520 ops/ms
b.IntStr.stringBuilder1 thrpt 20 28106.692 1117.958 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20066.939 1052.937 ops/ms
b.IntStr.stringFormat thrpt 20 2346.452 37.422 ops/ms
Тобто використання StringBuilder
класу відбувається повільніше через додаткові накладні витрати на створення StringBuilder
об’єкта та додавання порожнього рядка. Використання String.format(String, ...)
відбувається навіть повільніше, на порядок чи близько того.
З іншого боку, розповсюджуваний компілятор базується на OpenJDK 1.7:
java version "1.7.0_55"
OpenJDK Runtime Environment (mageia-2.4.7.1.mga4-x86_64 u55-b13)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
Результати тут були цікаві :
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 31249.306 881.125 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39486.857 663.766 ops/ms
b.IntStr.stringBuilder1 thrpt 20 41072.058 484.353 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20513.913 466.130 ops/ms
b.IntStr.stringFormat thrpt 20 2068.471 44.964 ops/ms
Чому StringBuilder.append(int)
ця JVM з’являється набагато швидше? Перегляд StringBuilder
вихідного коду класу не виявив нічого особливо цікавого - розглянутий метод майже ідентичний Integer#toString(int)
. Цікаво, що додавання результату Integer.toString(int)
( stringBuilder2
мікровизначення), здається, не швидше.
Чи є ця розбіжність у роботі проблемою з тестовим джгутом? Або мій OpenJDK JVM містить оптимізацію, яка вплине на цей конкретний (анти) шаблон?
РЕДАГУВАТИ:
Для більш прямого порівняння я встановив Oracle JDK 1.7u55:
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)
Результати схожі на результати OpenJDK:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32502.493 501.928 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39592.174 428.967 ops/ms
b.IntStr.stringBuilder1 thrpt 20 40978.633 544.236 ops/ms
Здається, це більш загальна проблема Java 7 проти Java 8. Можливо, Java 7 мала більш агресивну оптимізацію рядків?
РЕДАКТУВАТИ 2 :
Для повноти, ось параметри віртуальної машини, пов’язані із рядками, для обох цих JVM:
Для Oracle JDK 8u5:
$ /usr/java/default/bin/java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
Для OpenJDK 1.7:
$ java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
bool UseStringCache = false {product}
UseStringCache
Варіант був видалений в Java 8, без заміни, так що я сумніваюся , що робить ніякої різниці. Інші параметри мають однакові налаштування.
EDIT 3:
Бок про бік порівняння вихідного коду з AbstractStringBuilder
, StringBuilder
і Integer
класів з src.zip
файлу показує нічого noteworty. Окрім цілого ряду косметичних змін та змін у документації, Integer
тепер є певна підтримка цілих чисел без підпису та StringBuilder
була трохи реконструйована для надання більше коду StringBuffer
. Здається, жодна з цих змін не впливає на шляхи коду StringBuilder#append(int)
, якими я користувався, хоча я, можливо, щось пропустив.
Порівняння коду збірки, створеного для, IntStr#integerToString()
і IntStr#stringBuilder0()
є набагато цікавішим. Базовий макет коду, сформованого для, IntStr#integerToString()
був подібним для обох JVM, хоча Oracle JDK 8u5, здавалося, був більш агресивним, вбудовуючи деякі дзвінки всередині Integer#toString(int)
коду. Існувала чітка кореспонденція з вихідним кодом Java, навіть для тих, хто мав мінімальний досвід складання.
Однак код збірки для IntStr#stringBuilder0()
кардинально відрізнявся. Код, створений Oracle JDK 8u5, знову був безпосередньо пов'язаний з вихідним кодом Java - я легко міг розпізнати той самий макет. Навпаки, код, сформований OpenJDK 7, був майже невпізнанним для нетренованого ока (як мій). new StringBuilder()
Виклик був , здавалося б , видалений, як це було створення масиву в StringBuilder
конструкторі. Крім того, плагін дизассемблера не зміг надати стільки посилань на вихідний код, як у JDK 8.
Я припускаю, що це або результат набагато агресивнішого проходження оптимізації в OpenJDK 7, або, швидше за все, результат вставки рукописного низькорівневого коду для певних StringBuilder
операцій. Я не впевнений, чому ця оптимізація не відбувається в моїй реалізації JVM 8 або чому ті самі оптимізації не були реалізовані Integer#toString(int)
в JVM 7. Я думаю, хтось, знайомий із пов'язаними частинами вихідного коду JRE, повинен відповісти на ці питання ...
new StringBuilder().append(this.counter++).toString();
і третій тест зreturn "" + this.counter++;
?