我需要具有字节值的枚举常量,可以在超高性能排序系统中使用。但是,当我需要从相应的字节值获取枚举时,这会出现问题。 IE fromValue()。
我想知道当我想要高度优化的东西或者我应该坚持使用静态常量时,以下使用Byte
值映射到常量的方法是否被认为是一个坏主意。我试图避免的是循环遍历枚举值以在运行时找到正确的值,我相信这会在执行数百万次操作时增加不必要的开销。
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);
}
}
由于
更新
我创建了一些测试来查看各种方法的性能。第一种方法使用散列映射,第二种方法使用循环值,第三种数组偏移量,第四种数组偏移量使用in而不是字节(要查看从byte到int的向上转换是否有性能影响),第五种方法使用一个开关。
平均值超过100次运行,每次运行每次执行1亿次fromValue()调用。时间是以毫秒为单位(我从纳米时代改变了这一点,因为它因为更大的值而在我身上爆炸)。
结果如下:
和代码:
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);
}
}
答案 0 :(得分:2)
显然,阵列是最快的解决方案。像
这样的东西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;
}
除了可能是硬编码的switch
或if
之外,不会被任何东西殴打(尽管我强烈怀疑)。
因为这很可能比其他操作更快(不知怎的,你必须得到byte
并且结果以某种方式使用),我会停在这里。无需超越此优化。
如果您的字节值不同,您需要以不同的方式初始化数组,并且可能也会使其更长,但不会发生任何变化。
将JNI用于像数组访问这样简单的事情,就像使用飞机去洗手间一样高效。它很复杂,它有很大的开销,但也可能是一个很酷的因素。
答案 1 :(得分:1)
我希望切换比使用数组“明显”更快。在您的情况下,编译器可以优化switch语句(请参阅Why does Java switch on contiguous ints appear to run faster with added cases?)。
我怀疑这些测试无论如何都提供了任何有用的数字,但我尝试了使用switch
的第五个测试用例,我得到了以下结果。
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;
}
}
}