Измеряем производительность String.format() в Java |
||
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Психология ИИ Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Творчество ИИ Техническое зрение Чат-боты Авторизация |
2021-09-29 14:10 Бэкграунд Я раньше считал, что JDK в целом хорошо оптимизирована, и если в JDK есть простой способ решения какой-то задачи, то он вполне подойдет для большинства ситуаций и будет работать хорошо. Но я обнаружил, что иногда некоторые классы или методы работают на удивление плохо. Знание таких аномалий полезно при работе с требовательным к производительности кодом. В этом посте рассмотрим один из подобных кейсов: поразительно низкая производительность Более конкретно… Случай, на который я обратил внимание — это конкатенация строк. Например, получение составных ключей: String key = String.format("%s.%s", keyspace, tableName); Это эквивалентно следующему: String key = keyspace + "." + tableName; // или key = new StringBuilder().append(keyspace) .append('.').append(tableName).toString(); Можно ожидать одинаковую производительность всех этих вариантов, учитывая, что, с точки зрения пользователя, они выполняют одну и ту же операцию. Но у String.format() должны быть низкие накладные расходы? Следует отметить, что мы рассматриваем очень простой пример. Возможности Важно отметить, что при каждом вызове Учитывая это, разумно предположить, что в нашем простом случае с конкатенацией у Но действительно ли будут? Неужели разработчики JDK не совершили здесь еще один впечатляющий подвиг? Позовем на помощь нашего друга JMH Исходный код JMH-теста ( Вы можете запустить тест следующим образом: java -jar target/microbenchmarks.jar StringConcatenation На своем компьютере (Mac Mini (2018) 3.2Ghz 6-core Intel Core i7) я получил следующие результаты: m1_StringFormat thrpt 15 61337.088 ± 654.370 ops/s m2_StringBuilder thrpt 15 2683849.107 ± 22092.481 ops/s m3_StringBuilderPrealloc thrpt 15 2654994.965 ± 36881.162 ops/s m4_ManualConcatenation thrpt 15 2700825.252 ± 27906.924 ops/s Ваши цифры, конечно, будут немного отличаться. Тест-кейсы Прежде чем перейдем к анализу результатов, приведу описания тест-кейсов:
Во всех тестах конкатенируются пары строк в цикле с 32 итерациями. Тестовые строки довольно просты и могут быть не репрезентативны, но у меня нет цели воспроизвести конкретный кейс (хотя вам следует это сделать, если у вас есть конкретные данные!). Разбираемся с результатами Итак, давайте посмотрим на полученные результаты. Тесты m2, m3 и m4 дают примерно одинаковый результат: около 2,5 миллионов итераций в секунду. При 32 конкатенациях это составит около 80 миллионов конкатенаций в секунду (не забудьте также про различные накладные расходы, такие как сборка мусора). Неплохо. Но в первом случае (использование То есть в нашем случае Первые три случая интересны только тем, что m3 (предварительное выделение буфера в Это может быть связано с тем, что мы используем относительно небольшие строки, для которых размер буфера Но что происходит внутри String.format()? Можем ли мы выяснить, что происходит под капотом Для начала, чтобы JMH-тесты в классе выполнялись эффективно бесконечно (номинально в течение 1 часа вместо 5 секунд на форк), изменим параметры теста в // @Measurement(iterations = 5, time = 1) @Measurement(iterations = 3600, time = 1) Затем запустим "бесконечный" тест m1 с помощью: java -jar target/microbenchmarks.jar StringConcatenation.m1 После запуска посмотрим id процесса (через top) и запустим профилирование в течение 30 секунд: ~/bin/async-profiler -e cpu -d 30 -f ~/profile-string-format.txt 67640 Мы указали, что хотим использовать профилирование процессора в течение 30 секунд, результаты записать в указанный файл в виде текста (есть и другие форматы, такие как json) и профилировать процесс Java с идентификатором 67640 (вам нужно будет использовать идентификатор вашего запущенного процесса). Через 30 секунд мы получим файл с результатами (у меня 2592 строки) со сводкой в конце: ns percent samples top ---------- ------- ------- --- 3100000000 10.97% 310 java.util.regex.Pattern$Start.match 2970000000 10.51% 297 java.util.regex.Pattern$GroupHead.match 2370000000 8.39% 237 java.util.Formatter.format 2110000000 7.47% 211 java.lang.AbstractStringBuilder.ensureCapacityInternal 1590000000 5.63% 159 jshort_disjoint_arraycopy 1420000000 5.02% 142 java.util.Formatter$FormatSpecifier.index 1340000000 4.74% 134 java.util.Formatter.parse 1270000000 4.49% 127 arrayof_jint_fill 1240000000 4.39% 124 java.util.regex.Pattern$BmpCharProperty.match 1100000000 3.89% 110 java.util.regex.Pattern.matcher 990000000 3.50% 99 java.util.Formatter$FormatSpecifier.width 980000000 3.47% 98 java.util.regex.Pattern$Branch.match 8 ... Глядя на две верхние записи, мы видим, что для декодирования шаблона "%s.%s" используются регулярные выражения (подготовительный этап, о котором я говорил). Третью запись понять немного сложно, но есть несколько других методов с упоминанием Есть еще интересный момент: внутреннее перераспределение Плохая новость в том, что, похоже, нет никакого способа сделать какой-то препроцессинг Насколько это все важно? Влияние на производительность, как всегда, зависит от вашего конкретного случая. Что касается меня, то я использую
И избегаю использования в критических участках кода, включая циклы. Источник: m.vk.com Комментарии: |
|