/*
* Copyright (C) 2012 The Guava Authors
*
* 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.google.common.base;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import java.util.Arrays;
import java.util.Iterator;
/**
* Benchmarks {@link Joiner} against some common implementations of delimiter-based
* string joining.
*
* @author Adomas Paltanavicius
*/
public class JoinerBenchmark {
private static final String DELIMITER_STRING = ",";
private static final char DELIMITER_CHARACTER = ',';
private static final Joiner JOINER_ON_STRING = Joiner.on(DELIMITER_STRING);
private static final Joiner JOINER_ON_CHARACTER = Joiner.on(DELIMITER_CHARACTER);
@Param({"3", "30", "300"}) int count;
@Param({"0", "1", "16", "32", "100"}) int componentLength;
private Iterable<String> components;
@BeforeExperiment
void setUp() {
String component = Strings.repeat("a", componentLength);
String[] raw = new String[count];
Arrays.fill(raw, component);
components = Arrays.asList(raw);
}
/**
* {@link Joiner} with a string delimiter.
*/
@Benchmark int joinerWithStringDelimiter(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
dummy ^= JOINER_ON_STRING.join(components).length();
}
return dummy;
}
/**
* {@link Joiner} with a character delimiter.
*/
@Benchmark int joinerWithCharacterDelimiter(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
dummy ^= JOINER_ON_CHARACTER.join(components).length();
}
return dummy;
}
/**
* Mimics what the {@link Joiner} class does internally when no extra options like
* ignoring {@code null} values are used.
*/
@Benchmark int joinerInlined(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder();
Iterator<String> iterator = components.iterator();
if (iterator.hasNext()) {
sb.append(iterator.next().toString());
while (iterator.hasNext()) {
sb.append(DELIMITER_STRING);
sb.append(iterator.next());
}
}
dummy ^= sb.toString().length();
}
return dummy;
}
/**
* Only appends delimiter if the accumulated string is non-empty.
* Note: this isn't a candidate implementation for Joiner since it fails on leading
* empty components.
*/
@Benchmark int stringBuilderIsEmpty(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder();
for (String comp : components) {
if (sb.length() > 0) {
sb.append(DELIMITER_STRING);
}
sb.append(comp);
}
dummy ^= sb.toString().length();
}
return dummy;
}
/**
* Similar to the above, but keeps a boolean flag rather than checking for the string
* accumulated so far being empty. As a result, it does not have the above-mentioned bug.
*/
@Benchmark int booleanIfFirst(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder();
boolean append = false;
for (String comp : components) {
if (append) {
sb.append(DELIMITER_STRING);
}
sb.append(comp);
append = true;
}
dummy ^= sb.toString().length();
}
return dummy;
}
/**
* Starts with an empty delimiter and changes to the desired value at the end of the
* iteration.
*/
@Benchmark int assignDelimiter(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder();
String delim = "";
for (String comp : components) {
sb.append(delim);
sb.append(comp);
delim = DELIMITER_STRING;
}
dummy ^= sb.toString().length();
}
return dummy;
}
/**
* Always append the delimiter after the component, and in the very end shortens the buffer
* to get rid of the extra trailing delimiter.
*/
@Benchmark int alwaysAppendThenBackUp(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder();
for (String comp : components) {
sb.append(comp);
sb.append(DELIMITER_STRING);
}
if (sb.length() > 0) {
sb.setLength(sb.length() - DELIMITER_STRING.length());
}
dummy ^= sb.toString().length();
}
return dummy;
}
}