/*
* 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.facebook.presto.operator.scalar;
import io.airlift.slice.DynamicSliceOutput;
import io.airlift.slice.Slice;
import io.airlift.slice.Slices;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.VerboseMode;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
import static com.facebook.presto.operator.scalar.StringFunctions.leftTrim;
import static com.facebook.presto.operator.scalar.StringFunctions.length;
import static com.facebook.presto.operator.scalar.StringFunctions.lower;
import static com.facebook.presto.operator.scalar.StringFunctions.reverse;
import static com.facebook.presto.operator.scalar.StringFunctions.rightTrim;
import static com.facebook.presto.operator.scalar.StringFunctions.substr;
import static com.facebook.presto.operator.scalar.StringFunctions.trim;
import static com.facebook.presto.operator.scalar.StringFunctions.upper;
import static io.airlift.slice.Slices.utf8Slice;
import static java.lang.Character.MAX_CODE_POINT;
import static java.lang.Character.SURROGATE;
import static java.lang.Character.getType;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.openjdk.jmh.annotations.Mode.AverageTime;
import static org.openjdk.jmh.annotations.Scope.Thread;
@SuppressWarnings("MethodMayBeStatic")
@State(Thread)
@OutputTimeUnit(NANOSECONDS)
@BenchmarkMode(AverageTime)
@Fork(1)
@Warmup(iterations = 4, time = 500, timeUnit = MILLISECONDS)
@Measurement(iterations = 5, time = 500, timeUnit = MILLISECONDS)
public class StringFunctionsBenchmark
{
@Benchmark
public long benchmarkLength(BenchmarkData data)
{
return length(data.getSlice());
}
@Benchmark
public Slice benchmarkSubstringStart(BenchmarkData data)
{
Slice slice = data.getSlice();
int length = data.getLength();
return substr(slice, (length / 2) - 1);
}
@Benchmark
public Slice benchmarkSubstringStartLength(BenchmarkData data)
{
Slice slice = data.getSlice();
int length = data.getLength();
return substr(slice, (length / 2) - 1, length / 2);
}
@Benchmark
public Slice benchmarkSubstringStartFromEnd(BenchmarkData data)
{
Slice slice = data.getSlice();
int length = data.getLength();
return substr(slice, -((length / 2) + 1));
}
@Benchmark
public Slice benchmarkSubstringStartLengthFromEnd(BenchmarkData data)
{
Slice slice = data.getSlice();
int length = data.getLength();
return substr(slice, -((length / 2) + 1), length / 2);
}
@Benchmark
public Slice benchmarkReverse(BenchmarkData data)
{
return reverse(data.getSlice());
}
@Benchmark
public Slice benchmarkLeftTrim(WhitespaceData data)
{
return leftTrim(data.getLeftWhitespace());
}
@Benchmark
public Slice benchmarkRightTrim(WhitespaceData data)
{
return rightTrim(data.getRightWhitespace());
}
@Benchmark
public Slice benchmarkTrim(WhitespaceData data)
{
return trim(data.getBothWhitespace());
}
@Benchmark
public Slice benchmarkUpper(BenchmarkData data)
{
return upper(data.getSlice());
}
@Benchmark
public Slice benchmarkLower(BenchmarkData data)
{
return lower(data.getSlice());
}
@State(Thread)
public static class BenchmarkData
{
private static final int[] ASCII_CODE_POINTS;
private static final int[] ALL_CODE_POINTS;
static {
ASCII_CODE_POINTS = IntStream.range(0, 0x7F)
.toArray();
ALL_CODE_POINTS = IntStream.range(0, MAX_CODE_POINT)
.filter(codePoint -> getType(codePoint) != SURROGATE)
.toArray();
}
@Param({ "2", "5", "10", "100", "1000", "10000" })
private int length;
@Param({ "true", "false" })
private boolean ascii;
private Slice slice;
private int[] codePoints;
@Setup
public void setup()
{
int[] codePointSet = ascii ? ASCII_CODE_POINTS : ALL_CODE_POINTS;
ThreadLocalRandom random = ThreadLocalRandom.current();
codePoints = new int[length];
DynamicSliceOutput sliceOutput = new DynamicSliceOutput(length * 4);
for (int i = 0; i < codePoints.length; i++) {
int codePoint = codePointSet[random.nextInt(codePointSet.length)];
codePoints[i] = codePoint;
sliceOutput.appendBytes(new String(Character.toChars(codePoint)).getBytes(StandardCharsets.UTF_8));
}
slice = sliceOutput.slice();
}
public Slice getSlice()
{
return slice;
}
public int getLength()
{
return length;
}
}
@State(Thread)
public static class WhitespaceData
{
private static final int[] ASCII_WHITESPACE;
private static final int[] ALL_WHITESPACE;
static {
ASCII_WHITESPACE = IntStream.range(0, 0x7F)
.filter(Character::isWhitespace)
.toArray();
ALL_WHITESPACE = IntStream.range(0, MAX_CODE_POINT)
.filter(Character::isWhitespace)
.toArray();
}
@Param({ "2", "5", "10", "100", "1000", "10000" })
private int length;
@Param({ "true", "false" })
private boolean ascii;
private Slice leftWhitespace;
private Slice rightWhitespace;
private Slice bothWhitespace;
@Setup
public void setup()
{
Slice whitespace = createRandomUtf8Slice(ascii ? ASCII_WHITESPACE : ALL_WHITESPACE, length + 1);
leftWhitespace = Slices.copyOf(whitespace);
leftWhitespace.setByte(leftWhitespace.length() - 1, 'X');
rightWhitespace = Slices.copyOf(whitespace);
rightWhitespace.setByte(0, 'X');
bothWhitespace = Slices.copyOf(whitespace);
bothWhitespace.setByte(length / 2, 'X');
}
private static Slice createRandomUtf8Slice(int[] codePointSet, int length)
{
int[] codePoints = new int[length];
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < codePoints.length; i++) {
int codePoint = codePointSet[random.nextInt(codePointSet.length)];
codePoints[i] = codePoint;
}
return utf8Slice(new String(codePoints, 0, codePoints.length));
}
public int getLength()
{
return length;
}
public Slice getLeftWhitespace()
{
return leftWhitespace;
}
public Slice getRightWhitespace()
{
return rightWhitespace;
}
public Slice getBothWhitespace()
{
return bothWhitespace;
}
}
public static void main(String[] args)
throws RunnerException
{
Options options = new OptionsBuilder()
.verbosity(VerboseMode.NORMAL)
.include(".*" + StringFunctionsBenchmark.class.getSimpleName() + ".*")
.build();
new Runner(options).run();
}
}