As promised, here are two JMH benchmarks; one comparing Character#toUpperCase
to your bitwise method, and the other comparing Character#toLowerCase
to your other bitwise method. Note that only characters within the English alphabet were tested.
First Benchmark (to uppercase):
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
public class Test {
@Param({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"})
public char c;
@Benchmark
public char toUpperCaseNormal() {
return Character.toUpperCase(c);
}
@Benchmark
public char toUpperCaseBitwise() {
return (char) (c & 65503);
}
}
Output:
Benchmark (c) Mode Cnt Score Error Units
Test.toUpperCaseNormal a avgt 30 2.447 ± 0.028 ns/op
Test.toUpperCaseNormal b avgt 30 2.438 ± 0.035 ns/op
Test.toUpperCaseNormal c avgt 30 2.506 ± 0.083 ns/op
Test.toUpperCaseNormal d avgt 30 2.411 ± 0.010 ns/op
Test.toUpperCaseNormal e avgt 30 2.417 ± 0.010 ns/op
Test.toUpperCaseNormal f avgt 30 2.412 ± 0.005 ns/op
Test.toUpperCaseNormal g avgt 30 2.410 ± 0.004 ns/op
Test.toUpperCaseBitwise a avgt 30 1.758 ± 0.007 ns/op
Test.toUpperCaseBitwise b avgt 30 1.789 ± 0.032 ns/op
Test.toUpperCaseBitwise c avgt 30 1.763 ± 0.005 ns/op
Test.toUpperCaseBitwise d avgt 30 1.763 ± 0.012 ns/op
Test.toUpperCaseBitwise e avgt 30 1.757 ± 0.003 ns/op
Test.toUpperCaseBitwise f avgt 30 1.755 ± 0.003 ns/op
Test.toUpperCaseBitwise g avgt 30 1.759 ± 0.003 ns/op
Second Benchmark (to lowercase):
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
public class Test {
@Param({"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"})
public char c;
@Benchmark
public char toLowerCaseNormal() {
return Character.toUpperCase(c);
}
@Benchmark
public char toLowerCaseBitwise() {
return (char) (c | 32);
}
}
Output:
Benchmark (c) Mode Cnt Score Error Units
Test.toLowerCaseNormal A avgt 30 2.084 ± 0.007 ns/op
Test.toLowerCaseNormal B avgt 30 2.079 ± 0.006 ns/op
Test.toLowerCaseNormal C avgt 30 2.081 ± 0.005 ns/op
Test.toLowerCaseNormal D avgt 30 2.083 ± 0.010 ns/op
Test.toLowerCaseNormal E avgt 30 2.080 ± 0.005 ns/op
Test.toLowerCaseNormal F avgt 30 2.091 ± 0.020 ns/op
Test.toLowerCaseNormal G avgt 30 2.116 ± 0.061 ns/op
Test.toLowerCaseBitwise A avgt 30 1.708 ± 0.006 ns/op
Test.toLowerCaseBitwise B avgt 30 1.705 ± 0.018 ns/op
Test.toLowerCaseBitwise C avgt 30 1.721 ± 0.022 ns/op
Test.toLowerCaseBitwise D avgt 30 1.718 ± 0.010 ns/op
Test.toLowerCaseBitwise E avgt 30 1.706 ± 0.009 ns/op
Test.toLowerCaseBitwise F avgt 30 1.704 ± 0.004 ns/op
Test.toLowerCaseBitwise G avgt 30 1.711 ± 0.007 ns/op
I've only included a few different letters (even though all were tested), as they are all share similar outputs.
It's clear that your bitwise methods are faster, mainly due to Character#toUpperCase
and Character#toLowerCase
performing logical checks (as I had mentioned earlier today in my comment).