1 /* 2 * Copyright (C) 2012 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.base; 18 19 import com.google.caliper.BeforeExperiment; 20 import com.google.caliper.Benchmark; 21 import com.google.caliper.Param; 22 import java.util.Arrays; 23 import java.util.Iterator; 24 25 /** 26 * Benchmarks {@link Joiner} against some common implementations of delimiter-based string joining. 27 * 28 * @author Adomas Paltanavicius 29 */ 30 public class JoinerBenchmark { 31 32 private static final String DELIMITER_STRING = ","; 33 private static final char DELIMITER_CHARACTER = ','; 34 35 private static final Joiner JOINER_ON_STRING = Joiner.on(DELIMITER_STRING); 36 private static final Joiner JOINER_ON_CHARACTER = Joiner.on(DELIMITER_CHARACTER); 37 38 @Param({"3", "30", "300"}) 39 int count; 40 41 @Param({"0", "1", "16", "32", "100"}) 42 int componentLength; 43 44 private Iterable<String> components; 45 46 @BeforeExperiment setUp()47 void setUp() { 48 String component = Strings.repeat("a", componentLength); 49 String[] raw = new String[count]; 50 Arrays.fill(raw, component); 51 components = Arrays.asList(raw); 52 } 53 54 /** {@link Joiner} with a string delimiter. */ 55 @Benchmark joinerWithStringDelimiter(int reps)56 int joinerWithStringDelimiter(int reps) { 57 int dummy = 0; 58 for (int i = 0; i < reps; i++) { 59 dummy ^= JOINER_ON_STRING.join(components).length(); 60 } 61 return dummy; 62 } 63 64 /** {@link Joiner} with a character delimiter. */ 65 @Benchmark joinerWithCharacterDelimiter(int reps)66 int joinerWithCharacterDelimiter(int reps) { 67 int dummy = 0; 68 for (int i = 0; i < reps; i++) { 69 dummy ^= JOINER_ON_CHARACTER.join(components).length(); 70 } 71 return dummy; 72 } 73 74 /** 75 * Mimics what the {@link Joiner} class does internally when no extra options like ignoring {@code 76 * null} values are used. 77 */ 78 @Benchmark joinerInlined(int reps)79 int joinerInlined(int reps) { 80 int dummy = 0; 81 for (int i = 0; i < reps; i++) { 82 StringBuilder sb = new StringBuilder(); 83 Iterator<String> iterator = components.iterator(); 84 if (iterator.hasNext()) { 85 sb.append(iterator.next().toString()); 86 while (iterator.hasNext()) { 87 sb.append(DELIMITER_STRING); 88 sb.append(iterator.next()); 89 } 90 } 91 dummy ^= sb.toString().length(); 92 } 93 return dummy; 94 } 95 96 /** 97 * Only appends delimiter if the accumulated string is non-empty. Note: this isn't a candidate 98 * implementation for Joiner since it fails on leading empty components. 99 */ 100 @Benchmark stringBuilderIsEmpty(int reps)101 int stringBuilderIsEmpty(int reps) { 102 int dummy = 0; 103 for (int i = 0; i < reps; i++) { 104 StringBuilder sb = new StringBuilder(); 105 for (String comp : components) { 106 if (sb.length() > 0) { 107 sb.append(DELIMITER_STRING); 108 } 109 sb.append(comp); 110 } 111 dummy ^= sb.toString().length(); 112 } 113 return dummy; 114 } 115 116 /** 117 * Similar to the above, but keeps a boolean flag rather than checking for the string accumulated 118 * so far being empty. As a result, it does not have the above-mentioned bug. 119 */ 120 @Benchmark booleanIfFirst(int reps)121 int booleanIfFirst(int reps) { 122 int dummy = 0; 123 for (int i = 0; i < reps; i++) { 124 StringBuilder sb = new StringBuilder(); 125 boolean append = false; 126 for (String comp : components) { 127 if (append) { 128 sb.append(DELIMITER_STRING); 129 } 130 sb.append(comp); 131 append = true; 132 } 133 dummy ^= sb.toString().length(); 134 } 135 return dummy; 136 } 137 138 /** 139 * Starts with an empty delimiter and changes to the desired value at the end of the iteration. 140 */ 141 @Benchmark assignDelimiter(int reps)142 int assignDelimiter(int reps) { 143 int dummy = 0; 144 for (int i = 0; i < reps; i++) { 145 StringBuilder sb = new StringBuilder(); 146 String delim = ""; 147 for (String comp : components) { 148 sb.append(delim); 149 sb.append(comp); 150 delim = DELIMITER_STRING; 151 } 152 dummy ^= sb.toString().length(); 153 } 154 return dummy; 155 } 156 157 /** 158 * Always append the delimiter after the component, and in the very end shortens the buffer to get 159 * rid of the extra trailing delimiter. 160 */ 161 @Benchmark alwaysAppendThenBackUp(int reps)162 int alwaysAppendThenBackUp(int reps) { 163 int dummy = 0; 164 for (int i = 0; i < reps; i++) { 165 StringBuilder sb = new StringBuilder(); 166 for (String comp : components) { 167 sb.append(comp); 168 sb.append(DELIMITER_STRING); 169 } 170 if (sb.length() > 0) { 171 sb.setLength(sb.length() - DELIMITER_STRING.length()); 172 } 173 dummy ^= sb.toString().length(); 174 } 175 return dummy; 176 } 177 } 178