Я столкнулся с интересным сценарием. По какой-то причине strip() против пустой строки (содержит только пробелы) значительно быстрее, чем trim() в Java 11.
Контрольный показатель
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);
}
}
Результаты
# 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
Судя по всему, strip() превосходит trim() ~ в 5 раз.
Хотя для непустой строки результаты почти идентичны:
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
Почему? Это ошибка или я ошибаюсь?
Тестовая среда
Добавлены дополнительные тесты производительности для разных строк (пустые, пустые и т. д.).
Контрольный показатель
@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);
}
}
Результаты
# 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
Среда тестирования такая же.
Только одна интересная находка. Строка, содержащая символы Юникода, которые trim() обрабатываются быстрее, чем strip().




Изучив исходный код OpenJDK, предполагая, что реализация версии Oracle аналогична, я мог бы предположить, что разница объясняется тем, что
strip попытается найти первый непробельный символ, и если он не найден, просто вернет "".trim всегда возвращает new String(...the substring...)Можно возразить, что strip немного более оптимизирован, чем trim, по крайней мере, в OpenJDK, потому что он уклоняется от создания нового объекта без необходимости.
(Примечание: я не потрудился проверить версии этих методов в Юникоде.)
В OpenJDK 11.0.1 String.strip() (на самом деле StringLatin1.strip()) оптимизирует разделение на пустой String, возвращая интернированную константу String:
public static String strip(byte[] value) {
int left = indexOfNonWhitespace(value);
if (left == value.length) {
return "";
}
в то время как String.trim() (на самом деле StringLatin1.trim()) всегда выделяет новый объект String. В вашем примере st = 3 и len = 3, поэтому
return ((st > 0) || (len < value.length)) ?
newString(value, st, len - st) : null;
скопирует массив под капотом и создаст новый объект String
return new String(Arrays.copyOfRange(val, index, index + len),
LATIN1);
Делая вышеупомянутое предположение, мы можем обновить тест для сравнения с непустым String, на который не должна влиять упомянутая оптимизация String.strip():
@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();
}
}
Его запуск не показывает существенной разницы между strip() и trim() для непустого String. Как ни странно, обрезка до пустого String по-прежнему самая медленная:
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
Спасибо за объяснение! Мне интересно, почему разработчики JDK не оптимизировали trim() так же, как работает strip(). 5x - огромная разница в производительности.
Ага. В Java 11 или более ранней версии кажется, что .trim () всегда создает новую строку (), но strip () возвращает строку кеша. Вы можете протестировать этот простой код и убедиться в этом сами.
public class JavaClass{
public static void main(String[] args){
//prints false
System.out.println(" ".trim()= = "");//CREATING A NEW STRING()
}
}
против
public class JavaClass{
public static void main(String[] args){
//prints true
System.out.println(" ".strip()= = "");//RETURNING CACHE ""
}
}
strip()новее ... (не используетgetChar{unicode}, проверяет только завершающие символы на пустые строки, возвращает""{literal} вместоnew String(bytes))