Hello, I'd like to contribute this trivial patch to JDK.
The idea behind it is that with compact strings we don't need char[] when all
symbols are ASCII.
This allows to slightly reduce footprint of java.lang.Integer.class and drop
casts from char to byte.
I've measured performance of patched code using Integer/Long.toString() and
toHexString() methods for short and long values
and the patch is likely to improve it slightly, at least for Long:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g", "-XX:+UseParallelGC"})
public class IntegerToString {
@Benchmark
public String integerToHexString(Data data) { return
Integer.toHexString(data.value); }
@Benchmark
public String integerToString(Data data) { return
Integer.toString(data.value); }
@State(Scope.Thread)
public static class Data {
@Param({"0", "2147483647"}) private int value;
}
}
Gives
Benchmark (value) Mode Cnt Score
Error Units
IntegerToString.integerToHexString 0 avgt 50 7.833 ±
0.172 ns/op
IntegerToString.integerToString 0 avgt 50 7.550 ±
0.070 ns/op
IntegerToString.integerToHexString 2147483647 avgt 50 13.200 ±
0.268 ns/op
IntegerToString.integerToString 2147483647 avgt 50 17.028 ±
0.572 ns/op
after
Benchmark (value) Mode Cnt Score
Error Units
IntegerToString.integerToHexString 0 avgt 50 7.771 ±
0.410 ns/op
IntegerToString.integerToString 0 avgt 50 7.779 ±
0.321 ns/op
IntegerToString.integerToHexString 2147483647 avgt 50 13.193 ±
0.450 ns/op
IntegerToString.integerToString 2147483647 avgt 50 16.641 ±
0.332 ns/op
As of Long the corresponding benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g", "-XX:+UseParallelGC"})
public class LongToString {
@Benchmark
public String longToHexString(Data data) { return
Long.toHexString(data.longValue); }
@Benchmark
public String longToString(Data data) { return Long.toString(data.longValue);
}
@State(Scope.Thread)
public static class Data {
@Param({"0", "9223372036854775807"}) private long longValue;
}
}
gives
before
LongToString.longToHexString 0 avgt 50 7.711 ±
0.303 ns/op
LongToString.longToString 0 avgt 50 7.321 ±
0.439 ns/op
LongToString.longToHexString 9223372036854775807 avgt 50 17.393 ±
0.515 ns/op
LongToString.longToString 9223372036854775807 avgt 50 30.192 ±
1.365 ns/op
after
LongToString.longToHexString 0 avgt 50 7.796 ±
0.250 ns/op
LongToString.longToString 0 avgt 50 6.497 ±
0.015 ns/op
LongToString.longToHexString 9223372036854775807 avgt 50 17.720 ±
0.914 ns/op
LongToString.longToString 9223372036854775807 avgt 50 27.089 ±
0.232 ns/op
Also I've included into that patch trivial change for Integer.IntegerCache
where we can use local variable size
instead of c.length where c is array. This is likely to slightly improve
startup time as this code is executed at class
loading time and likely to be executed in interpreter mode rather than
beingcompiled by optimizing compiler.
Regards,
Sergey Tsypanov
diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java
--- a/src/java.base/share/classes/java/lang/Integer.java
+++ b/src/java.base/share/classes/java/lang/Integer.java
@@ -87,7 +87,7 @@
/**
* All possible chars for representing a number as a String
*/
- static final char[] digits = {
+ static final byte[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
@@ -159,10 +159,10 @@
}
while (i <= -radix) {
- buf[charPos--] = (byte)digits[-(i % radix)];
+ buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
- buf[charPos] = (byte)digits[-i];
+ buf[charPos] = digits[-i];
if (negative) {
buf[--charPos] = '-';
@@ -371,7 +371,7 @@
int radix = 1 << shift;
int mask = radix - 1;
do {
- buf[--charPos] = (byte)Integer.digits[val & mask];
+ buf[--charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (charPos > 0);
}
@@ -1030,7 +1030,7 @@
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
- for(int i = 0; i < c.length; i++) {
+ for(int i = 0; i < size; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java
--- a/src/java.base/share/classes/java/lang/Long.java
+++ b/src/java.base/share/classes/java/lang/Long.java
@@ -145,10 +145,10 @@
}
while (i <= -radix) {
- buf[charPos--] = (byte)Integer.digits[(int)(-(i % radix))];
+ buf[charPos--] = Integer.digits[(int)(-(i % radix))];
i = i / radix;
}
- buf[charPos] = (byte)Integer.digits[(int)(-i)];
+ buf[charPos] = Integer.digits[(int)(-i)];
if (negative) {
buf[--charPos] = '-';
@@ -413,7 +413,7 @@
int radix = 1 << shift;
int mask = radix - 1;
do {
- buf[--charPos] = (byte)Integer.digits[((int) val) & mask];
+ buf[--charPos] = Integer.digits[((int) val) & mask];
val >>>= shift;
} while (charPos > offset);
}