Pourquoi est la chaîne.strip() 5 fois plus rapide que String.trim () pour une chaîne vide en Java 11
J'ai rencontré un scénario intéressant. Pour une raison quelconque, strip()
contre une chaîne vide (contient uniquement des espaces blancs) nettement plus rapide que trim()
dans Java 11.
Référence
public class Test {
public static final String TEST_STRING = " "; // 3 whitespaces
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testTrim() {
TEST_STRING.trim();
}
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testStrip() {
TEST_STRING.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
Les Résultats
# Run complete. Total time: 00:04:16
Benchmark Mode Cnt Score Error Units
Test.testStrip thrpt 200 2067457963.295 ± 12353310.918 ops/s
Test.testTrim thrpt 200 402307182.894 ± 4559641.554 ops/s
Apparemment strip()
surpasse trim()
~5 fois.
Bien que pour une chaîne non vide, les résultats soient presque identiques:
public class Test {
public static final String TEST_STRING = " Test String ";
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testTrim() {
TEST_STRING.trim();
}
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testStrip() {
TEST_STRING.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
# Run complete. Total time: 00:04:16
Benchmark Mode Cnt Score Error Units
Test.testStrip thrpt 200 126939018.461 ± 1462665.695 ops/s
Test.testTrim thrpt 200 141868439.680 ± 1243136.707 ops/s
Comment se fait-il? Est-ce un bug ou est-ce que je le fais de mal?
Environnement de Test
- Il s'agit d'un processeur Intel Xeon E3 - 1585L v5 à 3,00 GHz.]}
- SYSTÈME d'exploitation - Windows 7 SP 1 64 bits
- JVM Oracle JDK 11.0.1 [33]}Benchamrk-JMH v 1.19
Mise à jour
Ajout de tests de performance pour différentes chaînes (vide, vide, etc.).
Référence
@Warmup(iterations = 5, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = SECONDS)
@Fork(value = 3)
@BenchmarkMode(Mode.Throughput)
public class Test {
private static final String BLANK = ""; // Blank
private static final String EMPTY = " "; // 3 spaces
private static final String ASCII = " abc "; // ASCII characters only
private static final String UNICODE = " абв "; // Russian Characters
private static final String BIG = EMPTY.concat("Test".repeat(100)).concat(EMPTY);
@Benchmark
public void blankTrim() {
BLANK.trim();
}
@Benchmark
public void blankStrip() {
BLANK.strip();
}
@Benchmark
public void emptyTrim() {
EMPTY.trim();
}
@Benchmark
public void emptyStrip() {
EMPTY.strip();
}
@Benchmark
public void asciiTrim() {
ASCII.trim();
}
@Benchmark
public void asciiStrip() {
ASCII.strip();
}
@Benchmark
public void unicodeTrim() {
UNICODE.trim();
}
@Benchmark
public void unicodeStrip() {
UNICODE.strip();
}
@Benchmark
public void bigTrim() {
BIG.trim();
}
@Benchmark
public void bigStrip() {
BIG.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
Les Résultats
# Run complete. Total time: 00:05:23
Benchmark Mode Cnt Score Error Units
Test.asciiStrip thrpt 15 356846913.133 ± 4096617.178 ops/s
Test.asciiTrim thrpt 15 371319467.629 ± 4396583.099 ops/s
Test.bigStrip thrpt 15 29058105.304 ± 1909323.104 ops/s
Test.bigTrim thrpt 15 28529199.298 ± 1794655.012 ops/s
Test.blankStrip thrpt 15 1556405453.206 ± 67230630.036 ops/s
Test.blankTrim thrpt 15 1587932109.069 ± 19457780.528 ops/s
Test.emptyStrip thrpt 15 2126290275.733 ± 23402906.719 ops/s
Test.emptyTrim thrpt 15 406354680.805 ± 14359067.902 ops/s
Test.unicodeStrip thrpt 15 37320438.099 ± 399421.799 ops/s
Test.unicodeTrim thrpt 15 88226653.577 ± 1628179.578 ops/s
L'environnement de test est le même.
Un seul découverte intéressante. Chaîne qui contient des caractères Unicode obtenant trim()
'ed plus rapidement que strip()
' ed
3 answers
Sur OpenJDK 11.0.1 String.strip()
(en fait StringLatin1.strip()
) optimise le décapage à un String
vide en renvoyant une constante String
internée:
public static String strip(byte[] value) {
int left = indexOfNonWhitespace(value);
if (left == value.length) {
return "";
}
Alors que String.trim()
(en fait StringLatin1.trim()
) alloue toujours un nouvel objet String
. Dans votre exemple st = 3
et len = 3
, donc
return ((st > 0) || (len < value.length)) ?
newString(value, st, len - st) : null;
Va sous le capot copier le tableau et crée un nouvel objet String
return new String(Arrays.copyOfRange(val, index, index + len),
LATIN1);
En faisant l'hypothèse ci-dessus, nous pouvons mettre à jour le benchmark pour le comparer à un String
non vide qui ne devrait pas être affecté par {[5 mentionné]} optimisation:
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public class MyBenchmark {
public static final String EMPTY_STRING = " "; // 3 whitespaces
public static final String NOT_EMPTY_STRING = " a "; // 3 whitespaces with a in the middle
@Benchmark
public void testEmptyTrim() {
EMPTY_STRING.trim();
}
@Benchmark
public void testEmptyStrip() {
EMPTY_STRING.strip();
}
@Benchmark
public void testNotEmptyTrim() {
NOT_EMPTY_STRING.trim();
}
@Benchmark
public void testNotEmptyStrip() {
NOT_EMPTY_STRING.strip();
}
}
En cours d'exécution, il ne montre aucune différence significative entre strip()
et trim()
pour un String
non vide. Curieusement, le rognage vers un String
vide est toujours le plus lent:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testEmptyStrip thrpt 100 1887848947.416 ± 257906287.634 ops/s
MyBenchmark.testEmptyTrim thrpt 100 206638996.217 ± 57952310.906 ops/s
MyBenchmark.testNotEmptyStrip thrpt 100 399701777.916 ± 2429785.818 ops/s
MyBenchmark.testNotEmptyTrim thrpt 100 385144724.856 ± 3928016.232 ops/s
Après avoir examiné le code source d'OpenJDK, en supposant que l'implémentation de la version Oracle est similaire, j'imagine que la différence s'explique par les faits que
-
strip
va essayer de trouver le premier caractère non-espace, et si aucun n'est trouvé, renvoie simplement""
-
trim
retournera toujours unnew String(...the substring...)
On pourrait soutenir que strip
est juste un tout petit peu plus optimisé que trim
, du moins dans OpenJDK, car il esquive la création de nouveaux objet sauf si nécessaire.
(Note: Je n'ai pas pris la peine de vérifier les versions unicode de ces méthodes.)
Oui. En Java 11 ou plus tôt semble que .trim() crée toujours une nouvelle chaîne() mais strip () renvoie une chaîne de cache. Vous pouvez tester ce code simple et le prouver vous-même.
public class JavaClass{
public static void main(String[] args){
//prints false
System.out.println(" ".trim()=="");//CREATING A NEW STRING()
}
}
Vs
public class JavaClass{
public static void main(String[] args){
//prints true
System.out.println(" ".strip()=="");//RETURNING CACHE ""
}
}