From 5eab155c48c2a997743bef0eeee1a71f99aebe83 Mon Sep 17 00:00:00 2001 From: PROgrammer_JARvis Date: Wed, 19 Feb 2020 18:39:55 +0300 Subject: [PATCH] Fix major issues in `PlaceholdersBenchmark` (resolves #66) --- .../PlaceholdersBenchmark.java | 200 ++++++++++++++++++ .../StaticPlaceholderBenchmark.java | 191 ----------------- 2 files changed, 200 insertions(+), 191 deletions(-) create mode 100644 ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/PlaceholdersBenchmark.java delete mode 100644 ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/StaticPlaceholderBenchmark.java diff --git a/ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/PlaceholdersBenchmark.java b/ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/PlaceholdersBenchmark.java new file mode 100644 index 00000000..7252e7c9 --- /dev/null +++ b/ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/PlaceholdersBenchmark.java @@ -0,0 +1,200 @@ +package ru.progrm_jarvis.ultimatemessenger; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import lombok.val; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import ru.progrm_jarvis.ultimatemessenger.format.StringFormatter; +import ru.progrm_jarvis.ultimatemessenger.format.model.AsmTextModelFactory; +import ru.progrm_jarvis.ultimatemessenger.format.model.SimpleTextModelFactory; +import ru.progrm_jarvis.ultimatemessenger.format.model.TextModel; +import ru.progrm_jarvis.ultimatemessenger.format.placeholder.Placeholders; +import ru.progrm_jarvis.ultimatemessenger.format.placeholder.SimplePlaceholders; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +public class PlaceholdersBenchmark { + + public static void main(@NonNull final String[] args) throws RunnerException { + new Runner(new OptionsBuilder().build()).run(); + } + + @Benchmark + public void regex(final Blackhole blackhole, + final GenericConfiguration genericConfiguration, + final RegexConfiguration regexConfiguration) { + for (val target : genericConfiguration.targets) { + val matcher = regexConfiguration.placeholderPattern.matcher(regexConfiguration.rawText); + + val result = new StringBuffer(); // this is not okay, but hi Java 8 support + while (matcher.find()) { + matcher.appendReplacement( + result, regexConfiguration.formatters.get(matcher.group(1)).apply(matcher.group(2), target) + ); + } + matcher.appendTail(result); + + blackhole.consume(result.toString()); + } + } + + @Benchmark + public void simplePlaceholdersWithSimpleTmf(final Blackhole blackhole, + final GenericConfiguration genericConfiguration, + final SimpleTmfPlaceholdersConfiguration placeholdersConfiguration) { + for (val target : genericConfiguration.targets) blackhole + .consume(placeholdersConfiguration.text.getText(target)); + } + + @Benchmark + public void simplePlaceholdersWithAsmWithScfTmf(final Blackhole blackhole, + final GenericConfiguration genericConfiguration, + final AsmTmfWithScfPlaceholdersConfiguration + placeholdersConfiguration) { + for (val target : genericConfiguration.targets) blackhole + .consume(placeholdersConfiguration.text.getText(target)); + } + + @Benchmark + public void simplePlaceholdersWithAsmWithoutScfTmf(final Blackhole blackhole, + final GenericConfiguration genericConfiguration, + final AsmTmfWithoutScfPlaceholdersConfiguration + placeholdersConfiguration) { + for (val target : genericConfiguration.targets) blackhole + .consume(placeholdersConfiguration.text.getText(target)); + } + + /** + * Basic generated configuration. + */ + @State(Scope.Benchmark) + @FieldDefaults(level = AccessLevel.PRIVATE) + public static class GenericConfiguration { + + /** + * Mix of random {@link FormattingTarget formatting targets} + */ + private final FormattingTarget[] targets = { + new FormattingTarget("JARvis", "PROgrammer", 18, 0xCAFEBABE), + new FormattingTarget("Julik", "Domino", 18, 0xCAFEBABE), + new FormattingTarget("Magical", "Guy", 0x1D0C0, 0b1001010010), + new FormattingTarget("Magical", "Guy", 0x1D0C0, 0b1001010010), // duplicate value put specially + new FormattingTarget("John", "Five", 48, 10.0) + }; + + /** + * Formatter for replacing placeholders related to {@link FormattingTarget} + */ + private final StringFormatter formatter = (text, target) -> { + switch (text) { + case "name": return target.getName(); + case "surname": return target.getSurname(); + case "age": return Integer.toString(target.getAge()); + case "score": return Double.toString(target.getScore()); + default: return ""; + } + }; + + /** + * Text which should be formatted + */ + @Param({ + "This is a simple string with no placeholders", + "This is a small string with some placeholders: {user:name} and {user:surname}", + "Text: {user:name}{user:surname} is known to be of age {user:age} and his score is {user:score}", + "Repeated: {user:name} (user:name) {user:score}{user:score} {user:age}&{user:surname}", + }) + private String rawText; + } + + @State(Scope.Benchmark) + public static class RegexConfiguration { + protected final Pattern placeholderPattern = Pattern.compile("\\{([^:}]*)(?::([^}]*))?}"); + protected String rawText; + protected final Map> formatters = new HashMap<>(); + + @Setup + public void setup(final GenericConfiguration genericConfiguration) { + rawText = genericConfiguration.rawText; + formatters.put("user", genericConfiguration.formatter); + } + } + + @State(Scope.Benchmark) + public static class PlaceholdersConfiguration { + protected final Placeholders placeholders + = SimplePlaceholders.builder().build(); + + @Setup + public void setup(final GenericConfiguration genericConfiguration) { + placeholders.add("user", genericConfiguration.formatter); + } + } + + @State(Scope.Benchmark) + public static class SimpleTmfPlaceholdersConfiguration { + protected TextModel text; + + @Setup + public void setup(final GenericConfiguration genericConfiguration, + final PlaceholdersConfiguration placeholdersConfiguration) { + text = placeholdersConfiguration.placeholders + .parse(SimpleTextModelFactory.get(), genericConfiguration.rawText); + } + } + + @State(Scope.Benchmark) + public static class AsmTmfWithoutScfPlaceholdersConfiguration { + protected TextModel text; + + @Setup + public void setup(final GenericConfiguration genericConfiguration, + final PlaceholdersConfiguration placeholdersConfiguration) { + text = placeholdersConfiguration.placeholders.parse(AsmTextModelFactory.create( + AsmTextModelFactory.configuration().enableStringConcatFactory(false).build() + ), genericConfiguration.rawText); + } + } + + @State(Scope.Benchmark) + public static class AsmTmfWithScfPlaceholdersConfiguration { + protected TextModel text; + + @Setup + public void setup(final GenericConfiguration genericConfiguration, + final PlaceholdersConfiguration placeholdersConfiguration) { + text = placeholdersConfiguration.placeholders.parse(AsmTextModelFactory.create( + AsmTextModelFactory.configuration().enableStringConcatFactory(true).build() + ), genericConfiguration.rawText); + } + } + + /** + * This is a target used for formatting. It's methods should be used instead of direct field access to be more + * realistic. + */ + @Value + @FieldDefaults(level = AccessLevel.PRIVATE) + private static final class FormattingTarget { + String name, surname; + int age; + // non-final field to allow more dynamic invocations + @NonFinal double score; + + public double getScore() { + return ++score; + } + } +} diff --git a/ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/StaticPlaceholderBenchmark.java b/ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/StaticPlaceholderBenchmark.java deleted file mode 100644 index 92b2bf81..00000000 --- a/ultimate-messenger/src/benchmarks/java/ru/progrm_jarvis/ultimatemessenger/StaticPlaceholderBenchmark.java +++ /dev/null @@ -1,191 +0,0 @@ -package ru.progrm_jarvis.ultimatemessenger; - -import lombok.NonNull; -import lombok.val; -import lombok.var; -import org.jetbrains.annotations.Nullable; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.OptionsBuilder; -import ru.progrm_jarvis.javacommons.pair.Pair; -import ru.progrm_jarvis.javacommons.pair.SimplePair; -import ru.progrm_jarvis.ultimatemessenger.format.model.AsmTextModelFactory; -import ru.progrm_jarvis.ultimatemessenger.format.model.SimpleTextModelFactory; -import ru.progrm_jarvis.ultimatemessenger.format.model.TextModel; -import ru.progrm_jarvis.ultimatemessenger.format.placeholder.Placeholders; -import ru.progrm_jarvis.ultimatemessenger.format.placeholder.SimplePlaceholders; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.NANOSECONDS) -public class StaticPlaceholderBenchmark { - - public static void main(@NonNull final String[] args) throws RunnerException { - new Runner(new OptionsBuilder().build()).run(); - } - - @Benchmark - public void stringReplace(final Blackhole blackhole, - final Configuration configuration, - final StringReplaceConfiguration stringReplaceConfiguration) { - for (var text : stringReplaceConfiguration.texts) { - for (val replacement : configuration.replacements.entrySet()) text - = text.replace(replacement.getKey(), replacement.getValue()); - - blackhole.consume(text); - } - } - - @Benchmark - public void regex(final Blackhole blackhole, - final Configuration configuration, - final RegexConfiguration regexConfiguration) { - for (val text : regexConfiguration.texts) { - val matcher = regexConfiguration.placeholderPattern.matcher(text); - - val result = new StringBuffer(); // this is not okay, but hi Java 8 support - while (matcher.find()) { - val group = matcher.group(1); - matcher.appendReplacement(result, configuration.replacements.get(group)); - } - matcher.appendTail(result); - - blackhole.consume(result.toString()); - } - } - - @Benchmark - public void simplePlaceholdersWithSimpleTmf(final Blackhole blackhole, - final SimpleTmfPlaceholdersConfiguration placeholdersConfiguration) { - for (val text : placeholdersConfiguration.texts) blackhole.consume(text.getText(null)); - } - - @Benchmark - public void simplePlaceholdersWithAsmTmf(final Blackhole blackhole, - final AsmTmfPlaceholdersConfiguration placeholdersConfiguration) { - for (val text : placeholdersConfiguration.texts) blackhole.consume(text.getText(null)); - } - - /** - * Basic generated configuration. - */ - @State(Scope.Benchmark) - public static class Configuration { - protected final Map replacements = new HashMap<>(); - - protected final List> textPairs = new ArrayList<>(); - - @Param({"1", "2", "10", "30", "50", "100"}) - private int replacementsCount; - @Param({"1", "5", "10", "100", "1000"}) - private int rawTextsCount; - - @Setup - public void setup() { - val random = ThreadLocalRandom.current(); - - val indexedReplacements = new String[replacementsCount]; - for (var i = 0; i < replacementsCount; i++) { - final String placeholder = "placeholder#" + i, replacement = Integer.toHexString(random.nextInt()); - replacements.put(placeholder, replacement); - indexedReplacements[i] = replacement; - } - - for (var i = 0; i < rawTextsCount; i++) { - val raw = new StringBuilder(); - val formatted = new StringBuilder(); - - val textBlocks = random.nextInt(0xFF); - for (var blockIndex = random.nextBoolean() ? 0 : 1; - blockIndex < textBlocks; blockIndex++) if ((blockIndex & 0b1) == 0) { - // add plain text - val text = new StringBuilder(); - val textLength = random.nextInt(0xF); - - for (var textSubBlock = 0; textSubBlock < textLength; textSubBlock++) text - .append(Integer.toHexString(random.nextInt())); - - raw.append(text); - formatted.append(text); - } else { - val placeholerId = random.nextInt(replacementsCount); - raw.append("{placeholder#").append(placeholerId).append('}'); - formatted.append(indexedReplacements[placeholerId]); - } - - textPairs.add(SimplePair.of(raw.toString(), formatted.toString())); - } - } - } - - @State(Scope.Benchmark) - public static class StringReplaceConfiguration { - private final List texts = new ArrayList<>(); - - @Setup - public void setup(final Configuration configuration) { - for (val textPair : configuration.textPairs) texts.add(textPair.getFirst()); - } - } - - @State(Scope.Benchmark) - public static class RegexConfiguration { - // don't know why but Intellij forces escaping of `{` - private final Pattern placeholderPattern = Pattern.compile("\\{(.*?)}"); - private final List texts = new ArrayList<>(); - - @Setup - public void setup(final Configuration configuration) { - for (val textPair : configuration.textPairs) texts.add(textPair.getFirst()); - } - } - - @State(Scope.Benchmark) - public static class PlaceholdersConfiguration { - protected final Placeholders placeholders = SimplePlaceholders.builder().build(); - - @Setup - public void setup(final Configuration configuration) { - for (val replacement : configuration.replacements.entrySet()) - placeholders.add( - // it is fair to get the value each time from map as regex implementation will have to do so - replacement.getKey(), (source, target) -> replacement.getValue() - ); - } - } - - @State(Scope.Benchmark) - public static class SimpleTmfPlaceholdersConfiguration { - private final List> texts = new ArrayList<>(); - - @Setup - public void setup(final Configuration configuration, - final PlaceholdersConfiguration placeholdersConfiguration) { - val textModelFactory = SimpleTextModelFactory.get(); - for (val textPair : configuration.textPairs) texts - .add(placeholdersConfiguration.placeholders.parse(textModelFactory, textPair.getFirst())); - } - } - - @State(Scope.Benchmark) - public static class AsmTmfPlaceholdersConfiguration { - private final List> texts = new ArrayList<>(); - - @Setup - public void setup(final Configuration configuration, - final PlaceholdersConfiguration placeholdersConfiguration) { - val textModelFactory = AsmTextModelFactory.get(); - for (val textPair : configuration.textPairs) texts - .add(placeholdersConfiguration.placeholders.parse(textModelFactory, textPair.getFirst())); - } - } -}