Tout en étudiant pour un peu de débat w.r.t. à l'aide de "" + n
et Integer.toString(int)
pour convertir un entier primitif à une chaîne que j'ai écrit ce JMH microbenchmark:
@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;
}
}
J'ai couru avec la valeur par défaut JMH options avec les deux machines virtuelles Java qui existent sur ma machine Linux (up-to-date de Mageia 4 64 bits, Intel i7-3770 CPU, 32 go de RAM). La première JVM a été celui fourni avec Oracle JDK 8u5 64 bits:
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)
Avec cette JVM j'ai eu assez à ce que j'attendais:
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
I. e. à l'aide de l' StringBuilder
classe est plus lent en raison de la charge supplémentaire de la création de l' StringBuilder
objet et en ajoutant une chaîne vide. À l'aide de String.format(String, ...)
est encore plus lente, par un ordre de grandeur.
La distribution fournis par le compilateur, d'autre part, est basé sur 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)
Les résultats présentés ici ont été intéressants:
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
Pourquoi est - StringBuilder.append(int)
apparaissent de manière beaucoup plus rapide avec cette JVM? En regardant l' StringBuilder
code source de la classe n'a rien révélé particulièrement intéressant - la méthode en question est presque identique à l' Integer#toString(int)
. Fait intéressant à noter, en y ajoutant le résultat de l' Integer.toString(int)
( stringBuilder2
microbenchmark) ne semble pas être plus rapide.
Est-ce la performance divergence d'un problème avec le test de harnais? Ou est-ce que mon OpenJDK JVM contiennent des optimisations qui pourraient affecter ce code particulier (anti)-motif?
EDIT:
Pour une plus straight-forward de comparaison, j'ai installé 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)
Les résultats sont similaires à ceux de 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
Il semble que c'est plus général de Java 7 vs Java 8 question. Peut-être Java 7 n'a plus agressive de la chaîne d'optimisations?
EDIT 2:
Pour être complet, ici sont liés à la chaîne VM options pour ces deux machines virtuelles:
Pour 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}
Pour 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}
L' UseStringCache
option a été supprimée dans Java 8 avec pas de remplacement, donc je doute que cela fait toute la différence. Le reste des options semblent avoir les mêmes paramètres.
EDIT 3:
Un side-by-side de comparaison du code source de l' AbstractStringBuilder
, StringBuilder
et Integer
des classes de l' src.zip
le fichier de ne révèle rien de noteworty. En dehors de tout un tas de cosmétiques et de documentation des changements, Integer
a maintenant un certain soutien pour les entiers non signés et StringBuilder
a été légèrement remaniée pour partager du code avec StringBuffer
. Aucun de ces changements ne semblent influer sur les chemins de code utilisée par StringBuilder#append(int)
,, bien que j'ai peut-être raté quelque chose.
Une comparaison du code assembleur généré pour IntStr#integerToString()
et IntStr#stringBuilder0()
est beaucoup plus intéressante. La disposition de base du code généré pour IntStr#integerToString()
a été similaire pour les deux machines virtuelles, bien que Oracle JDK 8u5 semblait être plus agressif w.r.t. inline certains appels au sein de l' Integer#toString(int)
code. Il y a une correspondance avec le code source Java, même pour quelqu'un avec un minimum d'expérience de montage.
Le code assembleur pour IntStr#stringBuilder0()
, cependant, était radicalement différente. Le code généré par Oracle JDK 8u5 a été une fois de plus directement liées à la Java, le code source, j'ai pu facilement reconnaître la même mise en page. Au contraire, le code généré par OpenJDK 7 était presque méconnaissable à l'œil non averti (comme le mien). L' new StringBuilder()
appel a apparemment été supprimé, comme l'était la création de la matrice dans l' StringBuilder
constructeur. De plus, le désassembleur plugin n'a pas été en mesure de fournir autant de références pour le code source, comme il l'a fait dans le JDK 8.
Je suppose que c'est soit le résultat d'une beaucoup plus agressives d'optimisation de passer dans OpenJDK 7, ou plus probablement à la suite de l'insertion d'écrit à la main au code de bas niveau pour certains StringBuilder
des opérations. Je ne suis pas sûr pourquoi cette optimisation ne se fait pas dans mon JVM 8 mise en œuvre ou pourquoi les mêmes optimisations n'ont pas été mis en œuvre pour Integer#toString(int)
de la JVM 7. Je suppose que quelqu'un de familier avec les parties connexes de la JRE code source aurait à répondre à ces questions...