Java枚举的性能?
我正在实现一个2人游戏,它将在一个紧密的循环中运行数十万次,然后性能至上 我的代码实际上是这样的:Java枚举的性能?,java,performance,enums,Java,Performance,Enums,我正在实现一个2人游戏,它将在一个紧密的循环中运行数十万次,然后性能至上 我的代码实际上是这样的: public class Table { private final int WHITE_PLAYER = +1; private final int BLACK_PLAYER = -1; private final int currentPlayer; private final int otherPlayer; ... } aload_0 gets
public class Table {
private final int WHITE_PLAYER = +1;
private final int BLACK_PLAYER = -1;
private final int currentPlayer;
private final int otherPlayer;
...
}
aload_0
getstatic
if_acmpne
iload_0
iconst_1
if_icmpne
我想知道我是否会在性能上受到影响,我会选择替换吗
private final int WHITE_PLAYER = +1;
private final int BLACK_PLAYER = -1;
到定义为的枚举
public enum Players {
WhitePlayer,
BlackPlayer
}
我的想法是枚举只是整型常量上的语法糖,仔细看一下为测试枚举生成的字节码,以及调用它的代码,似乎表明使用它们确实与进行静态方法调用相同,但对于一些在第一次运行时设置的枚举基础设施而言
我的假设是,将枚举用作静态常量确实是一样的,这是正确的还是我遗漏了什么?在微观基准测试中,是的,检查整数常量相等比检查枚举常量相等要快 然而,在实际应用程序中,更不用说游戏了,这将是完全不相关的。AWT子系统(或任何其他GUI工具包)中发生的事情使这些微性能考虑因素相形见绌 编辑 那么,让我详细说明一下 枚举比较如下所示:
public class Table {
private final int WHITE_PLAYER = +1;
private final int BLACK_PLAYER = -1;
private final int currentPlayer;
private final int otherPlayer;
...
}
aload_0
getstatic
if_acmpne
iload_0
iconst_1
if_icmpne
小整数的整数比较如下所示:
public class Table {
private final int WHITE_PLAYER = +1;
private final int BLACK_PLAYER = -1;
private final int currentPlayer;
private final int otherPlayer;
...
}
aload_0
getstatic
if_acmpne
iload_0
iconst_1
if_icmpne
显然,第一种方法比第二种方法工作量大,尽管差别很小
运行以下测试用例:
class Test {
static final int ONE = 1;
static final int TWO = 2;
enum TestEnum {ONE, TWO}
public static void main(String[] args) {
testEnum();
testInteger();
time("enum", new Runnable() {
public void run() {
testEnum();
}
});
time("integer", new Runnable() {
public void run() {
testInteger();
}
});
}
private static void testEnum() {
TestEnum value = TestEnum.ONE;
for (int i = 0; i < 1000000000; i++) {
if (value == TestEnum.TWO) {
System.err.println("impossible");
}
}
}
private static void testInteger() {
int value = ONE;
for (int i = 0; i < 1000000000; i++) {
if (value == TWO) {
System.err.println("impossible");
}
}
}
private static void time(String name, Runnable runnable) {
long startTime = System.currentTimeMillis();
runnable.run();
System.err.println(name + ": " + (System.currentTimeMillis() - startTime) + " ms");
}
}
类测试{
静态最终整数=1;
静态最终int 2=2;
枚举TestEnum{1,2}
公共静态void main(字符串[]args){
睾丸();
testInteger();
时间(“枚举”,新的可运行(){
公开募捐{
睾丸();
}
});
时间(“整数”,新的可运行(){
公开募捐{
testInteger();
}
});
}
私有静态void testEnum(){
TestEnum值=TestEnum.ONE;
对于(int i=0;i<100000000;i++){
如果(值==TestEnum.TWO){
系统错误println(“不可能”);
}
}
}
私有静态void testinger(){
int值=1;
对于(int i=0;i<100000000;i++){
如果(值==2){
系统错误println(“不可能”);
}
}
}
私有静态无效时间(字符串名称,Runnable){
long startTime=System.currentTimeMillis();
runnable.run();
System.err.println(name+“:”+(System.currentTimeMillis()-startTime)+“ms”);
}
}
您会发现枚举比较比整数比较慢,在我的机器上大约慢1.5%
我想说的是,这种差异在实际应用中并不重要(“过早优化是万恶之源”)。我在专业的基础上处理性能问题(请参见我的个人资料),我从未见过这样的热点问题。在关注性能之前,您应该先关注好的可读代码。在您的评测结果显示枚举是瓶颈之前(不要猜测!),请忘记性能,使用更容易理解的工具。JIT将优化很多东西,使类似的东西在运行一段时间后变得无关紧要
更不用说,如果您在代码中出错,枚举更具可读性,也更简单。您的假设是正确的。Java确保枚举只有一个实例,因此==与比较int一样有效。我特别关心在switch语句中使用枚举。我将使用一个程序来计算在一个很长的数组中一组有限的符号出现的次数 首先定义一些常数
static final int NUMSYMBOLS = Integer.MAX_VALUE/100; // size of array
// Constants for symbols ZERO ... NINE
static final int ZERO_I =0, ONE_I =1, TWO_I =2, THREE_I =3, FOUR_I =4;
static final int FIVE_I =5, SIX_I =6, SEVEN_I =7, EIGHT_I =8, NINE_I =9;
和相应的枚举
enum Symbol {
ZERO (0), ONE (1), TWO (2), THREE (3), FOUR (4),
FIVE (5), SIX (6), SEVEN (7), EIGHT (8), NINE (9);
final int code;
Symbol(int num) {
code = num;
}
public final int getCode() {
return code;
}
}
枚举具有由构造函数设置的字段代码。我们将在测试中使用此代码
稍后,这可以产生一些加速
符号集存储在一个数组和一个对应的int数组中
Symbol[] symbolArray;
int[] intArray;
符号在一个方法中计数
void testEnum() {
for(int i=0;i<NUMSYMBOLS;++i) {
Symbol sym = symbolArray[i];
switch(sym) {
case ZERO: ++numZero; break;
case ONE: ++numOne; break;
case TWO: ++numTwo; break;
case THREE: ++numThree; break;
case FOUR: ++numFour; break;
case FIVE: ++numFive; break;
case SIX: ++numSix; break;
case SEVEN: ++numSeven; break;
case EIGHT: ++numEight; break;
case NINE: ++numNine; break;
default: break;
}
}
}
构建命令列表可以使用enum,而不需要知道实际的int值
Command com = new Command(OpCode.PUSH,"x");
对于代码的非关键部分,我们可以在交换机中使用enum。在命令的toString()方法中
public String toString() {
switch(op) {
case PUSH:
return "Push "+var;
....
}
}
但关键部分可以使用代码
public void evaluate(Command com) {
switch(com.code) {
case 0:
stack.push(com.var);
break;
....
}
}
为了那额外的表现
switch语句的字节码很有趣。在int示例中,swicth语句编译为:
private void testInteger(int);
Code:
0: iload_1
1: tableswitch { // 0 to 9
0: 56
1: 69
2: 82
3: 95
4: 108
5: 121
6: 134
7: 147
8: 160
9: 173
default: 186
}
56: aload_0
57: dup
58: getfield #151 // Field numZero:I
61: iconst_1
62: iadd
63: putfield #151 // Field numZero:I
66: goto 186
69: aload_0
70: dup
71: getfield #153 // Field numOne:I
74: iconst_1
75: iadd
76: putfield #153 // Field numOne:I
79: goto 186
....
tableswitch命令根据值在代码中有效地向前跳转
使用代码(或序号)的开关代码类似。只需额外调用getCode()方法
仅使用枚举,代码更复杂
private void testEnum(toys.EnumTest$Symbol);
Code:
0: invokestatic #176
// Method $SWITCH_TABLE$toys$EnumTest$Symbol:()[I
3: aload_1
4: invokevirtual #179 // Method toys/EnumTest$Symbol.ordinal:()I
7: iaload
8: tableswitch { // 1 to 10
1: 64
2: 77
3: 90
4: 103
5: 116
6: 129
7: 142
8: 155
9: 168
10: 181
default: 194
}
这里首先调用一个新方法$SWITCH_TABLE$toys$EnumTest$Symbol:()
此方法创建一个数组,将序数值转换为开关中使用的索引。基本上相当于
int[] lookups = get_SWITCH_TABLE();
int pos = array[sys.ordinal()];
switch(pos) {
...
}
switch表创建方法,在第一次调用时计算一次表,并在随后的每次调用中使用相同的表。因此,与整数情况相比,我们看到两个非常简单的函数调用和一个额外的数组查找。每多少时间单位数十万次?好问题。我估计有几天我会全天24小时不间断地进行比赛,所以比赛结束得越快,我就越早分析比赛结果。我恳请大家不要把它变成典型的对抗性能调优线程的战争。如果它认为它真的符合要求,我会对它进行测量。如果你不告诉我们你对枚举做了什么,我们就无法回答这个问题,即使我们想回答。@EnglumeDealysium我认为没有对抗性能调优的战争。大多数人只是建议不要过早优化。请参阅我的答案(严重被否决),即枚举比较要慢多少(~1.5%)