• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
23 import java.util.Arrays;
24 import java.util.Iterator;
25 
26 /**
27  * Benchmarks {@link Joiner} against some common implementations of delimiter-based
28  * string joining.
29  *
30  * @author Adomas Paltanavicius
31  */
32 public class JoinerBenchmark {
33 
34   private static final String DELIMITER_STRING = ",";
35   private static final char DELIMITER_CHARACTER = ',';
36 
37   private static final Joiner JOINER_ON_STRING = Joiner.on(DELIMITER_STRING);
38   private static final Joiner JOINER_ON_CHARACTER = Joiner.on(DELIMITER_CHARACTER);
39 
40   @Param({"3", "30", "300"}) int count;
41   @Param({"0", "1", "16", "32", "100"}) int componentLength;
42 
43   private Iterable<String> components;
44 
45   @BeforeExperiment
setUp()46   void setUp() {
47     String component = Strings.repeat("a", componentLength);
48     String[] raw = new String[count];
49     Arrays.fill(raw, component);
50     components = Arrays.asList(raw);
51   }
52 
53   /**
54    * {@link Joiner} with a string delimiter.
55    */
joinerWithStringDelimiter(int reps)56   @Benchmark 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   /**
65    * {@link Joiner} with a character delimiter.
66    */
joinerWithCharacterDelimiter(int reps)67   @Benchmark int joinerWithCharacterDelimiter(int reps) {
68     int dummy = 0;
69     for (int i = 0; i < reps; i++) {
70       dummy ^= JOINER_ON_CHARACTER.join(components).length();
71     }
72     return dummy;
73   }
74 
75   /**
76    * Mimics what the {@link Joiner} class does internally when no extra options like
77    * ignoring {@code null} values are used.
78    */
joinerInlined(int reps)79   @Benchmark 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.
98    * Note: this isn't a candidate implementation for Joiner since it fails on leading
99    * empty components.
100    */
stringBuilderIsEmpty(int reps)101   @Benchmark 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
118    * accumulated so far being empty. As a result, it does not have the above-mentioned bug.
119    */
booleanIfFirst(int reps)120   @Benchmark int booleanIfFirst(int reps) {
121     int dummy = 0;
122     for (int i = 0; i < reps; i++) {
123       StringBuilder sb = new StringBuilder();
124       boolean append = false;
125       for (String comp : components) {
126         if (append) {
127           sb.append(DELIMITER_STRING);
128         }
129         sb.append(comp);
130         append = true;
131       }
132       dummy ^= sb.toString().length();
133     }
134     return dummy;
135   }
136 
137   /**
138    * Starts with an empty delimiter and changes to the desired value at the end of the
139    * iteration.
140    */
assignDelimiter(int reps)141   @Benchmark int assignDelimiter(int reps) {
142     int dummy = 0;
143     for (int i = 0; i < reps; i++) {
144       StringBuilder sb = new StringBuilder();
145       String delim = "";
146       for (String comp : components) {
147         sb.append(delim);
148         sb.append(comp);
149         delim = DELIMITER_STRING;
150       }
151       dummy ^= sb.toString().length();
152     }
153     return dummy;
154   }
155 
156   /**
157    * Always append the delimiter after the component, and in the very end shortens the buffer
158    * to get rid of the extra trailing delimiter.
159    */
alwaysAppendThenBackUp(int reps)160   @Benchmark int alwaysAppendThenBackUp(int reps) {
161     int dummy = 0;
162     for (int i = 0; i < reps; i++) {
163       StringBuilder sb = new StringBuilder();
164       for (String comp : components) {
165         sb.append(comp);
166         sb.append(DELIMITER_STRING);
167       }
168       if (sb.length() > 0) {
169         sb.setLength(sb.length() - DELIMITER_STRING.length());
170       }
171       dummy ^= sb.toString().length();
172     }
173     return dummy;
174   }
175 }
176