/* * Copyright 2014-2017 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.netflix.spectator.perf; import com.netflix.spectator.impl.AsciiSet; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.infra.Blackhole; import java.util.UUID; /** * Compares a number of approaches for quickly replacing invalid characters that are part * of a string. The goal is to be fast and preferably minimize the number of allocations. * * The {@link AsciiSet} class uses a combination of array approach, check first, and method * handle to avoid an additional allocation and array copy when creating the string (see * {@link StringCreate} benchmark for more details). * * <pre> * Benchmark Mode Cnt Score Error Units * * StringReplace.bad_array thrpt 10 16714173.859 ± 234209.339 ops/s * StringReplace.bad_asciiSet thrpt 10 18107063.738 ± 360271.524 ops/s * StringReplace.bad_checkFirst thrpt 10 14165041.869 ± 208433.352 ops/s * StringReplace.bad_naive thrpt 10 530188.254 ± 8342.491 ops/s * StringReplace.bad_stringBuilder thrpt 10 5754313.189 ± 138045.465 ops/s * * StringReplace.ok_array thrpt 10 18899940.964 ± 293219.495 ops/s * StringReplace.ok_asciiSet thrpt 10 30230490.618 ± 1096911.677 ops/s * StringReplace.ok_checkFirst thrpt 10 30601837.288 ± 453161.602 ops/s * StringReplace.ok_naive thrpt 10 491380.343 ± 9738.662 ops/s * StringReplace.ok_stringBuilder thrpt 10 6673496.163 ± 115877.036 ops/s * </pre> */ @State(Scope.Thread) public class StringReplace { private final AsciiSet set = AsciiSet.fromPattern("-._A-Za-z0-9"); private final String ok = UUID.randomUUID().toString(); private final String bad = ok.replace('-', ' '); private void replace(StringBuilder buf, String input, char replacement) { final int n = input.length(); for (int i = 0; i < n; ++i) { final char c = input.charAt(i); if (set.contains(c)) { buf.append(c); } else { buf.append(replacement); } } } private void replace(char[] buf, String input, char replacement) { final int n = input.length(); for (int i = 0; i < n; ++i) { final char c = input.charAt(i); if (!set.contains(c)) { buf[i] = replacement; } } } private String naive(String input, char replacement) { return input.replaceAll("[-._A-Za-z0-9]", "" + replacement); } private String stringBuilder(String input, char replacement) { StringBuilder buf = new StringBuilder(input.length()); replace(buf, input, replacement); return buf.toString(); } private String array(String input, char replacement) { char[] buf = input.toCharArray(); replace(buf, input, replacement); return new String(buf); } private String checkFirst(String input, char replacement) { return set.containsAll(input) ? input : array(input, replacement); } private String asciiSet(String input, char replacement) { return set.replaceNonMembers(input, replacement); } @Threads(1) @Benchmark public void ok_naive(Blackhole bh) { bh.consume(naive(ok, '_')); } @Threads(1) @Benchmark public void ok_stringBuilder(Blackhole bh) { bh.consume(stringBuilder(ok, '_')); } @Threads(1) @Benchmark public void ok_array(Blackhole bh) { bh.consume(array(ok, '_')); } @Threads(1) @Benchmark public void ok_checkFirst(Blackhole bh) { bh.consume(checkFirst(ok, '_')); } @Threads(1) @Benchmark public void ok_asciiSet(Blackhole bh) { bh.consume(asciiSet(ok, '_')); } @Threads(1) @Benchmark public void bad_naive(Blackhole bh) { bh.consume(naive(bad, '_')); } @Threads(1) @Benchmark public void bad_stringBuilder(Blackhole bh) { bh.consume(stringBuilder(bad, '_')); } @Threads(1) @Benchmark public void bad_array(Blackhole bh) { bh.consume(array(bad, '_')); } @Threads(1) @Benchmark public void bad_checkFirst(Blackhole bh) { bh.consume(checkFirst(bad, '_')); } @Threads(1) @Benchmark public void bad_asciiSet(Blackhole bh) { bh.consume(asciiSet(bad, '_')); } }