Reputation: 1254
I need to have enum constants with byte values which can be used in a super performant sorting system. However this presents a problem when I need to get an enum from the corresponding byte value. IE fromValue().
I am wondering if the following approach of using a map of Byte
values to constants is considered a bad idea when I want something that is highly optimized, or if I should just stick with static constants. What I am trying to avoid is looping through the enum values to find the correct one at runtime, which I believe would add un-needed overhead when doing millions of these operations.
public enum ReferenceTargetType {
BINARY((byte)0x1),
TOPIC((byte)0x2),
MAP((byte)0x3),
UNKNOWN((byte)0x4);
private static Map<Byte,ReferenceTargetType> targetTypeMap = new HashMap<Byte,ReferenceTargetType>();
static {
for(ReferenceTargetType type : ReferenceTargetType.values()){
targetTypeMap.put(type.getValue(), type);
}
}
private byte value;
ReferenceTargetType(byte value){
this.value = value;
}
byte getValue(){
return this.value;
}
static ReferenceTargetType fromValue(byte value){
return targetTypeMap.get(value);
}
}
Thanks
UPDATE
I created some tests to look at performance of various methods. The first method uses a hashmap, the second by looping through values, the third array offsets, and the fourth array offsets with ints instead of bytes (To see if up casting from byte to int has a performance impact), the fifth uses a switch.
Averages are over 100 runs with each run doing a 100 million fromValue() calls each. Times are in ms (I changed this from nanotime because it was blowing up on me for the larger values).
Here are the results:
and the code:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.HashMap;
import java.util.Map;
@RunWith(JUnit4.class)
public class EnumFromValueTest {
static int masterRuns = 100;
static int runs = 100000000;
static long[] r1runs = new long[masterRuns];
static long[] r2runs = new long[masterRuns];
static long[] r3runs = new long[masterRuns];
static long[] r4runs = new long[masterRuns];
static long[] r5runs = new long[masterRuns];
static long average(long[] values){
int total = 0;
for(int i = 0; i < values.length; i++)
{
total += values[i];
}
int average = total / values.length;
return average;
}
public enum ReferenceTargetType1 {
BINARY((byte)0x0),
TOPIC((byte)0x1),
MAP((byte)0x2),
UNKNOWN((byte)0x3);
private static
Map<Byte,ReferenceTargetType1>
targetTypeMap = new HashMap<Byte, ReferenceTargetType1>();
static {
for(ReferenceTargetType1 type : ReferenceTargetType1.values()){
targetTypeMap.put(type.getValue(), type);
}
}
private byte value;
ReferenceTargetType1(byte value){
this.value = value;
}
byte getValue(){
return this.value;
}
static ReferenceTargetType1 fromValue(byte value){
return targetTypeMap.get(value);
}
}
public enum ReferenceTargetType2 {
BINARY((byte)0x0),
TOPIC((byte)0x1),
MAP((byte)0x2),
UNKNOWN((byte)0x3);
private byte value;
ReferenceTargetType2(byte value){
this.value = value;
}
byte getValue(){
return this.value;
}
static ReferenceTargetType2 fromValue(byte value){
for(ReferenceTargetType2 type : ReferenceTargetType2.values()){
if(type.getValue() == value)
return type;
}
return null;
}
}
public enum ReferenceTargetType3 {
BINARY((byte)0x0),
TOPIC((byte)0x1),
MAP((byte)0x2),
UNKNOWN((byte)0x3);
private byte value;
private static ReferenceTargetType3[] values = new ReferenceTargetType3[ReferenceTargetType3.values().length];
static {
int i = 0;
for(ReferenceTargetType3 type : ReferenceTargetType3.values()){
values[i]= type;
i++;
}
}
ReferenceTargetType3(byte value){
this.value = value;
}
byte getValue(){
return this.value;
}
static ReferenceTargetType3 fromValue(byte value){
return values[value];
}
}
public enum ReferenceTargetType4 {
BINARY(0),
TOPIC(1),
MAP(2),
UNKNOWN(3);
private int value;
private static ReferenceTargetType4[] values = new ReferenceTargetType4[ReferenceTargetType4.values().length];
static {
int i = 0;
for(ReferenceTargetType4 type : ReferenceTargetType4.values()){
values[i]= type;
i++;
}
}
ReferenceTargetType4(int value){
this.value = value;
}
int getValue(){
return this.value;
}
static ReferenceTargetType4 fromValue(int value){
return values[value];
}
}
public enum ReferenceTargetType5 {
BINARY((byte)0x0),
TOPIC((byte)0x1),
MAP((byte)0x2),
UNKNOWN((byte)0x3);
private byte value;
ReferenceTargetType5(byte value){
this.value = value;
}
byte getValue(){
return this.value;
}
static ReferenceTargetType5 fromValue(byte value) {
switch (value) {
case 0x0: return BINARY;
case 0x1: return TOPIC;
case 0x2: return BINARY;
case 0x3: return UNKNOWN;
default: return UNKNOWN;
}
}
}
@Test
public void doPerformanceTest(){
for(int i = 0; i < masterRuns;i++){
doRuns(i);
}
System.out.println("Run 1 average: " + average(r1runs));
System.out.println("Run 2 average: " + average(r2runs));
System.out.println("Run 3 average: " + average(r3runs));
System.out.println("Run 4 average: " + average(r4runs));
System.out.println("Run 5 average: " + average(r5runs));
}
public void doRuns(int runnum){
ReferenceTargetType1 type1 = ReferenceTargetType1.UNKNOWN;
ReferenceTargetType2 type2 = ReferenceTargetType2.UNKNOWN;
ReferenceTargetType3 type3 = ReferenceTargetType3.UNKNOWN;
ReferenceTargetType4 type4 = ReferenceTargetType4.UNKNOWN;
ReferenceTargetType5 type5 = ReferenceTargetType5.UNKNOWN;
long startTime1 = System.currentTimeMillis();
for(int i = 0; i < runs;i++){
ReferenceTargetType1.fromValue(type1.getValue());
}
r1runs[runnum] = (System.currentTimeMillis() - startTime1);
long startTime2 = System.currentTimeMillis();
for(int i = 0; i < runs;i++){
ReferenceTargetType2.fromValue(type2.getValue());
}
r2runs[runnum] = (System.currentTimeMillis() - startTime2);
long startTime3 = System.currentTimeMillis();
for(int i = 0; i < runs;i++){
ReferenceTargetType3.fromValue(type3.getValue());
}
r3runs[runnum] = (System.currentTimeMillis() - startTime3);
long startTime4 = System.currentTimeMillis();
for(int i = 0; i < runs;i++){
ReferenceTargetType4.fromValue(type4.getValue());
}
r4runs[runnum] = (System.currentTimeMillis() - startTime4);
long startTime5 = System.currentTimeMillis();
for(int i = 0; i < runs;i++){
ReferenceTargetType5.fromValue(type5.getValue());
}
r5runs[runnum] = (System.currentTimeMillis() - startTime5);
}
}
Upvotes: 0
Views: 793
Reputation: 29959
I'd expect a switch to be way faster than "obviously" using arrays. In your case the compiler can optimize switch statements (see Why does Java switch on contiguous ints appear to run faster with added cases?).
I doubt that these tests provide any useful numbers anyway, but I've tried a fifth test case using switch
and I get following results.
Run 1 average: 57729
Run 2 average: 93424
Run 3 average: 797
Run 4 average: 776
Run 5 average: 237
public enum ReferenceTargetType5 {
BINARY((byte) 0x0), TOPIC((byte) 0x1), MAP((byte) 0x2), UNKNOWN((byte) 0x3);
private byte value;
ReferenceTargetType5(byte value) {
this.value = value;
}
byte getValue() {
return this.value;
}
static ReferenceTargetType5 fromValue(byte value) {
switch (value) {
case 0x0: return BINARY;
case 0x1: return TOPIC;
case 0x2: return BINARY;
case 0x3: return UNKNOWN;
default: return UNKNOWN;
}
}
}
Upvotes: 1
Reputation: 46392
Obviously, an array is the fastest solution. Something like
private final static ReferenceTargetType TYPES = ReferenceTargetType.values();
public ReferenceTargetType byteToType(byte b) {
int index = b - 1;
if (0<=index && index<TYPES.length) return TYPES[index];
... throw SomeException or return null;
}
can't be beaten by anything, except maybe a hardcoded switch
or if
(though I strongly doubt it).
As this is most probably way faster then other operations (somehow you must get the byte
and the result gets somehow used), I'd stop here. No need to optimize beyond this.
If your byte values were different, you'd need to initialize the array differently and possibly also make it longer, but otherwise nothing changes.
Using JNI for something as simple as an array access is about as efficient as using an airplane to get to the bathroom. It's complicated, it has a huge overhead, but maybe a coolness factor, too.
Upvotes: 2