Java has byte, short, int, char and long integral primitive types and Java language specification describes them very well:
The integral types are byte, short, int, and long, whose values are 8-bit, 16-bit, 32-bit and 64-bit signed two’s-complement integers, respectively, and char, whose values are 16-bit unsigned integers representing UTF-16 code units
I found these types mentioned in all java materials that I read but I saw in practice int and long most of the time. The logical question is if we really need all of them. Common sense tells me that I should check from two perspectives: storage usage and operations speed. I need more time to prepare a test for storage usage so I started with operations speed.
I selected the following operations based on how often I saw them in code
- The multiplicative operators *, /, and % (§15.17)
- The additive operators + and – (§15.18)
- The increment operator ++, both prefix (§15.15.1) and postfix (§15.14.2)
- The decrement operator –, both prefix (§15.15.2) and postfix (§15.14.3)
and I prepared a jmh benchmark:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class IntegralPrimitivesOperations { byte bOp1 = 64; byte bOp2 = 21; @Benchmark public void baseline() { } /* * Addition */ @Benchmark public byte measureAddByte() { return (byte) (bOp1 + bOp2); } //... similar code for short, int, char and long /* * Subtraction */ @Benchmark public byte measureSubByte() { return (byte) (bOp1 - bOp2); } //... similar code for short, int, char and long /* * Multiplication */ @Benchmark public byte measureMultByte() { return (byte) (bOp1 * bOp2); } //... similar code for short, int, char and long /* * Division */ @Benchmark public byte measureDivByte() { return (byte) (bOp1 / bOp2); } //... similar code for short, int, char and long /* * Modulo */ @Benchmark public byte measureModByte() { return (byte) (bOp1 % bOp2); } //... similar code for short, int, char and long /* * Increment */ @Benchmark public byte measureIncByte() { return ++bOp1; } //... similar code for short, int, char and long /* * Decrement */ @Benchmark public byte measureDecByte() { return --bOp1; } //... similar code for short, int, char and long /* * All */ @Benchmark public byte measureAllByte() { return (byte) (++bOp1 * bOp2 + bOp1 / --bOp2 - --bOp1 % bOp2 + bOp2 * ++bOp1 - ++bOp2 / bOp1 + bOp2 % --bOp1); } //... similar code for short, int, char and long } |
I did not include all methods in this code excerpt, original code is available on github. I ran this benchmark three times and the results are:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
Benchmark Mode Cnt Score Error Score Error Score Error Units IntegralPrimitivesOperations.baseline avgt 10 0.319 ± 0.001 0.320 ± 0.001 0.319 ± 0.001 ns/op IntegralPrimitivesOperations.measureAddByte avgt 10 2.816 ± 0.014 2.819 ± 0.013 2.813 ± 0.010 ns/op IntegralPrimitivesOperations.measureAddChar avgt 10 2.823 ± 0.077 2.819 ± 0.070 2.809 ± 0.008 ns/op IntegralPrimitivesOperations.measureAddInt avgt 10 2.597 ± 0.006 2.596 ± 0.015 2.593 ± 0.005 ns/op IntegralPrimitivesOperations.measureAddLong avgt 10 2.592 ± 0.008 2.593 ± 0.005 2.612 ± 0.058 ns/op IntegralPrimitivesOperations.measureAddShort avgt 10 2.840 ± 0.066 2.815 ± 0.006 2.833 ± 0.066 ns/op IntegralPrimitivesOperations.measureSubByte avgt 10 2.829 ± 0.068 2.778 ± 0.011 2.794 ± 0.072 ns/op IntegralPrimitivesOperations.measureSubChar avgt 10 2.814 ± 0.023 2.787 ± 0.036 2.776 ± 0.010 ns/op IntegralPrimitivesOperations.measureSubInt avgt 10 2.593 ± 0.007 2.643 ± 0.008 2.646 ± 0.026 ns/op IntegralPrimitivesOperations.measureSubLong avgt 10 2.628 ± 0.174 2.654 ± 0.007 2.659 ± 0.005 ns/op IntegralPrimitivesOperations.measureSubShort avgt 10 2.811 ± 0.003 2.740 ± 0.014 2.740 ± 0.010 ns/op IntegralPrimitivesOperations.measureIncByte avgt 10 2.866 ± 0.148 2.814 ± 0.008 2.824 ± 0.036 ns/op IntegralPrimitivesOperations.measureIncChar avgt 10 2.805 ± 0.100 2.806 ± 0.007 2.814 ± 0.043 ns/op IntegralPrimitivesOperations.measureIncInt avgt 10 2.630 ± 0.008 2.592 ± 0.010 2.593 ± 0.015 ns/op IntegralPrimitivesOperations.measureIncLong avgt 10 2.649 ± 0.003 2.606 ± 0.012 2.642 ± 0.181 ns/op IntegralPrimitivesOperations.measureIncShort avgt 10 2.747 ± 0.033 2.828 ± 0.038 2.808 ± 0.009 ns/op IntegralPrimitivesOperations.measureDecByte avgt 10 2.774 ± 0.007 2.777 ± 0.008 2.781 ± 0.015 ns/op IntegralPrimitivesOperations.measureDecChar avgt 10 2.804 ± 0.084 2.787 ± 0.016 2.782 ± 0.009 ns/op IntegralPrimitivesOperations.measureDecInt avgt 10 2.807 ± 0.100 2.643 ± 0.021 2.642 ± 0.015 ns/op IntegralPrimitivesOperations.measureDecLong avgt 10 2.747 ± 0.077 2.659 ± 0.008 2.663 ± 0.006 ns/op IntegralPrimitivesOperations.measureDecShort avgt 10 2.837 ± 0.091 2.758 ± 0.072 2.764 ± 0.094 ns/op IntegralPrimitivesOperations.measureMultByte avgt 10 2.863 ± 0.030 2.841 ± 0.007 2.938 ± 0.074 ns/op IntegralPrimitivesOperations.measureMultChar avgt 10 2.842 ± 0.004 2.864 ± 0.075 2.943 ± 0.042 ns/op IntegralPrimitivesOperations.measureMultInt avgt 10 2.608 ± 0.032 2.600 ± 0.016 2.768 ± 0.258 ns/op IntegralPrimitivesOperations.measureMultLong avgt 10 2.781 ± 0.179 2.603 ± 0.010 2.753 ± 0.188 ns/op IntegralPrimitivesOperations.measureMultShort avgt 10 2.892 ± 0.071 2.841 ± 0.008 2.836 ± 0.003 ns/op IntegralPrimitivesOperations.measureDivByte avgt 10 5.099 ± 0.067 4.843 ± 0.016 4.844 ± 0.016 ns/op IntegralPrimitivesOperations.measureDivChar avgt 10 5.292 ± 0.045 4.998 ± 0.020 4.986 ± 0.009 ns/op IntegralPrimitivesOperations.measureDivInt avgt 10 5.010 ± 0.045 4.924 ± 0.056 4.845 ± 0.012 ns/op IntegralPrimitivesOperations.measureDivLong avgt 10 11.712 ± 0.270 11.210 ± 0.028 11.264 ± 0.069 ns/op IntegralPrimitivesOperations.measureDivShort avgt 10 5.188 ± 0.077 4.847 ± 0.020 4.846 ± 0.013 ns/op IntegralPrimitivesOperations.measureModByte avgt 10 4.843 ± 0.006 4.845 ± 0.010 4.864 ± 0.033 ns/op IntegralPrimitivesOperations.measureModChar avgt 10 4.853 ± 0.019 4.838 ± 0.006 4.855 ± 0.081 ns/op IntegralPrimitivesOperations.measureModInt avgt 10 4.886 ± 0.147 4.854 ± 0.015 5.026 ± 0.236 ns/op IntegralPrimitivesOperations.measureModLong avgt 10 11.357 ± 0.146 11.268 ± 0.030 11.566 ± 0.186 ns/op IntegralPrimitivesOperations.measureModShort avgt 10 4.941 ± 0.185 4.870 ± 0.036 5.095 ± 0.136 ns/op IntegralPrimitivesOperations.measureAllByte avgt 10 16.537 ± 0.100 16.549 ± 0.060 16.651 ± 0.075 ns/op IntegralPrimitivesOperations.measureAllChar avgt 10 13.803 ± 0.080 13.813 ± 0.071 13.842 ± 0.106 ns/op IntegralPrimitivesOperations.measureAllInt avgt 10 13.830 ± 0.022 13.855 ± 0.044 13.869 ± 0.101 ns/op IntegralPrimitivesOperations.measureAllLong avgt 10 41.685 ± 0.250 41.739 ± 0.320 41.740 ± 0.153 ns/op IntegralPrimitivesOperations.measureAllShort avgt 10 16.091 ± 0.051 16.125 ± 0.203 16.122 ± 0.282 ns/op |
Most of the operations performed similarly except / and % on long type and last
relatively complex expression that included all other operations.
The fact that division operations are more expensive on long type is not such a big surprise as the results of the last expression on byte and short. The same operations done isolated run in similar amount of time and done together take a bit longer. It can be from widening and cast between byte/short and int:
If an integer operator other than a shift operator has at least one operand of type long, then the operation is carried out using 64-bit precision, and the result of the numerical operator is of type long. If the other operand is not long, it is first widened (§5.1.5) to type long by numeric promotion (§5.6).
Otherwise, the operation is carried out using 32-bit precision, and the result of the numerical operator is of type int. If either operand is not an int, it is first widened to type int by numeric promotion.
These results are not enough to jump to a conclusion, let’s see in the next post what the difference from the storage perspective is.
Environment:
java version “1.8.0_66”
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
Image credit: morzaszum, CC0 Public Domain