CharSequenceUtilsBenchmark.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 *
 *      https://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 org.apache.commons.lang3;

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

/**
 * Benchmark comparing the old and new implementations of CharSequenceUtils methods.
 *
 * <p>
 * Run with:
 * </p>
 *
 * <pre>
 * mvn -P benchmark clean test -Dbenchmark=org.apache.commons.lang3.CharSequenceUtilsBenchmark
 * </pre>
 * <p>
 * Results:
 * </p>
 *
 * <pre>
Benchmark                                               (charSequenceType)  (length)  Mode  Cnt     Score    Error  Units
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent              String        10  avgt    5     1.626 ��  0.011  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent              String        50  avgt    5     2.741 ��  0.029  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent              String       100  avgt    5     4.235 ��  0.038  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent              String       500  avgt    5    17.713 ��  0.273  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent              String      1000  avgt    5    34.692 ��  1.752  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent       StringBuilder        10  avgt    5     1.963 ��  0.047  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent       StringBuilder        50  avgt    5     4.085 ��  0.042  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent       StringBuilder       100  avgt    5     5.978 ��  0.177  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent       StringBuilder       500  avgt    5    25.616 ��  1.621  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent       StringBuilder      1000  avgt    5    53.749 ��  0.420  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent        StringBuffer        10  avgt    5     7.239 ��  0.149  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent        StringBuffer        50  avgt    5     9.061 ��  0.187  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent        StringBuffer       100  avgt    5    10.281 ��  0.055  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent        StringBuffer       500  avgt    5    29.647 ��  0.420  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayCurrent        StringBuffer      1000  avgt    5    56.203 ��  0.505  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew                  String        10  avgt    5     1.657 ��  0.030  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew                  String        50  avgt    5     2.771 ��  0.094  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew                  String       100  avgt    5     4.281 ��  0.036  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew                  String       500  avgt    5    17.744 ��  0.091  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew                  String      1000  avgt    5    34.224 ��  0.251  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew           StringBuilder        10  avgt    5     1.962 ��  0.128  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew           StringBuilder        50  avgt    5     4.101 ��  0.035  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew           StringBuilder       100  avgt    5     5.984 ��  0.062  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew           StringBuilder       500  avgt    5    25.448 ��  0.152  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew           StringBuilder      1000  avgt    5    54.531 ��  0.559  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew            StringBuffer        10  avgt    5     7.260 ��  0.175  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew            StringBuffer        50  avgt    5     8.537 ��  0.101  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew            StringBuffer       100  avgt    5    10.502 ��  0.143  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew            StringBuffer       500  avgt    5    29.584 ��  0.339  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayNew            StringBuffer      1000  avgt    5    56.751 ��  0.983  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld                  String        10  avgt    5     1.656 ��  0.231  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld                  String        50  avgt    5     2.770 ��  0.222  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld                  String       100  avgt    5     4.298 ��  0.198  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld                  String       500  avgt    5    18.023 ��  0.203  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld                  String      1000  avgt    5    35.053 ��  1.467  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld           StringBuilder        10  avgt    5     3.164 ��  0.062  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld           StringBuilder        50  avgt    5     8.907 ��  0.185  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld           StringBuilder       100  avgt    5    15.801 ��  0.104  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld           StringBuilder       500  avgt    5    77.203 ��  0.460  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld           StringBuilder      1000  avgt    5   164.064 ��  2.506  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld            StringBuffer        10  avgt    5    28.981 ��  0.307  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld            StringBuffer        50  avgt    5   126.285 ��  1.688  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld            StringBuffer       100  avgt    5   250.584 ��  5.639  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld            StringBuffer       500  avgt    5  1231.478 �� 51.296  ns/op
CharSequenceUtilsBenchmark.benchmarkToCharArrayOld            StringBuffer      1000  avgt    5  2453.553 �� 54.004  ns/op
 * </pre>
 *
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
public class CharSequenceUtilsBenchmark {

    /**
     * New optimized implementation of toCharArray.
     */
    public static char[] toCharArrayNew(final CharSequence source) {
        final int len = StringUtils.length(source);
        if (len == 0) {
            return ArrayUtils.EMPTY_CHAR_ARRAY;
        }
        if (source instanceof String) {
            return ((String) source).toCharArray();
        }
        // NEW: Uses bulk getChars() for StringBuilder/StringBuffer
        if (source instanceof StringBuilder) {
            final char[] array = new char[len];
            ((StringBuilder) source).getChars(0, len, array, 0);
            return array;
        }
        if (source instanceof StringBuffer) {
            final char[] array = new char[len];
            ((StringBuffer) source).getChars(0, len, array, 0);
            return array;
        }
        final char[] array = new char[len];
        for (int i = 0; i < len; i++) {
            array[i] = source.charAt(i);
        }
        return array;
    }

    /**
     * Old implementation of toCharArray.
     */
    public static char[] toCharArrayOld(final CharSequence source) {
        final int len = StringUtils.length(source);
        if (len == 0) {
            return ArrayUtils.EMPTY_CHAR_ARRAY;
        }
        if (source instanceof String) {
            return ((String) source).toCharArray();
        }
        // OLD: Always uses charAt() loop, even for StringBuilder/StringBuffer
        final char[] array = new char[len];
        for (int i = 0; i < len; i++) {
            array[i] = source.charAt(i);
        }
        return array;
    }

    @Param({ "10", "50", "100", "500", "1000" })
    public int length;
    @Param({ "String", "StringBuilder", "StringBuffer" })
    public String charSequenceType;
    private CharSequence testSequence;

    @Benchmark
    public char[] benchmarkToCharArrayCurrent() {
        return CharSequenceUtils.toCharArray(testSequence);
    }
    @Benchmark
    public char[] benchmarkToCharArrayNew() {
        return toCharArrayNew(testSequence);
    }

    @Benchmark
    public char[] benchmarkToCharArrayOld() {
        return toCharArrayOld(testSequence);
    }

    @Setup(Level.Trial)
    public void setup() {
        final StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sb.append((char) ('a' + i % 26));
        }
        final String content = sb.toString();
        switch (charSequenceType) {
        case "String":
            testSequence = content;
            break;
        case "StringBuilder":
            testSequence = new StringBuilder(content);
            break;
        case "StringBuffer":
            testSequence = new StringBuffer(content);
            break;
        }
    }
}