答案针对@Riaan 对常量与方法枚举和性能原因的评论,它不直接回答 OP 问题,因此我认为它可以被视为噪音。
但是,我认为了解内部工作原理是一件重要的事情。
我从他的示例中删除了基准并对其进行了改进,以消除占用超过 90% 执行时间的垃圾收集和字符串创建。添加了预热阶段以确保热点实际编译方法。
还有更多,基准是有效的调用站点测试。调用站点的优化对于 1 来说是完全不同的,对于 2 来说更多一些和更多更多。调用点是对抽象(或只是被覆盖)方法的调用。
下面是 6 个枚举常量的测试:
package t1;
public class ZEnums {
public enum MyEnum {
A { boolean getBooleanValue(){ return true; }},
B { boolean getBooleanValue(){ return true; }},
C { boolean getBooleanValue(){ return false; }},
D { boolean getBooleanValue(){ return false; }},
E { boolean getBooleanValue(){ return false; }},
F { boolean getBooleanValue(){ return false; }},
;
abstract boolean getBooleanValue();
}
public enum MyEnumAlt {
A (true),
B (true),
C (false),
D (false),
E (false),
F (false),
;
private final boolean isTrue;
MyEnumAlt( boolean isTrue){ this.isTrue = isTrue; }
boolean getBooleanValue(){ return isTrue; };
}
public static void main(String[] args) {
log("Warming up...");
//10k iterations won't do since not all paths for MyEnum are invoked 10k (default) times to warrant compilations
long warmum = testEnum(100000 )+ testAlt(100000)+testEnum(100000 )+ testAlt(100000);
log("Warm up: %d", warmum);
//no info from +XX:+PrintCompilation below this one, or the test is invalid
testMain();
}
public static void testMain() {
int iterations = (int)4e7;
log("Testing %d iterations%n", iterations);
log("====");
log("Testing with Overridden method...");
System.gc();
{
long start = System.currentTimeMillis();
long len = 0;
len = testEnum(iterations);
long time = System.currentTimeMillis()-start;
log("Overridden method version took %dms, length: %d ", time, len);
}
////////////
System.gc();
{
log("Testing with Constant in c-tor... ");
long start = System.currentTimeMillis();
long len = testAlt(iterations);
long time = System.currentTimeMillis()-start;
log("Constant in c-tor version took %dms, length: %d ", time, len);
}
}
private static long testEnum(int iterations) {
long len = 0;
for(int i=0; i<iterations; i++){
MyEnum tmpEnum = MyEnum.A;
if(i%3==0){ tmpEnum = MyEnum.A;
}else if(i%4==0){ tmpEnum = MyEnum.B;
}else if(i%5==0){ tmpEnum = MyEnum.C;
}else if(i%6==0){ tmpEnum = MyEnum.D;
}else if(i%6==0){ tmpEnum = MyEnum.E;
}else{ tmpEnum = MyEnum.F;
}
String tmp = tmpEnum.getBooleanValue()?"XXX":"ABCDE";
len+=tmp.length();
}
return len;
}
private static long testAlt(int iterations) {
long len =0;
for(int i=0; i<iterations; i++){
MyEnumAlt tmpEnum = MyEnumAlt.A;
if(i%3==0){ tmpEnum = MyEnumAlt.A;
}else if(i%4==0){ tmpEnum = MyEnumAlt.B;
}else if(i%5==0){ tmpEnum = MyEnumAlt.C;
}else if(i%6==0){ tmpEnum = MyEnumAlt.D;
}else if(i%6==0){ tmpEnum = MyEnumAlt.E;
}else{ tmpEnum = MyEnumAlt.F;
}
String tmp = tmpEnum.getBooleanValue()?"XXX":"ABCDE";
len+=tmp.length();
}
return len;
}
static void log(String msg, Object... params){
String s = params.length>0?String.format(msg, params):msg;
System.out.printf("%tH:%<tM:%<tS.%<tL %s%n", new Long(System.currentTimeMillis()), s);
}
}
21:08:46.685 热身...
148 1% t1.ZEnums::testEnum @ 7 (125 字节)
150 1 t1.ZEnums$MyEnum$6::getBooleanValue (2 字节)
152 2 t1.ZEnums$MyEnum$1::getBooleanValue(2 字节)
154 3 t1.ZEnums$MyEnum$2::getBooleanValue(2 字节)
155 4 t1.ZEnums$MyEnum$3::getBooleanValue(2 字节)
158 2% t1.ZEnums::testAlt @ 7 (125 字节)
162 5 t1.ZEnums::testEnum (125 字节)
164 6 t1.ZEnums::testAlt (125 字节)
21:08:46.716 热身:1600000
21:08:46.716 测试 40000000 次迭代
21:08:46.716 ====
21:08:46.716 使用覆盖方法进行测试...
21:08:47.513
重写方法版本耗时 781ms,长度:160000000
21:08:47.513 在 c-tor 中使用常量进行测试...
21:08:48.138
c-tor 版本中的常量耗时 625ms,长度:160000000
代码使用-server -XX:+PrintCompilation 选项运行。
当然,差别并不大。然而,这不是有趣的问题。但是,如果您使用 2 个枚举常量测试版本,结果可能会大不相同。对于 2 个调用站点,编译器通过内联相关方法来生成代码。在上面的测试中,这将删除对 booleanValue 的整个调用,甚至可以在 O(1) 中执行测试。
然而,最有趣的部分是当编译器开始使用内联缓存然后是常量时,从 2 到 3 个枚举常量,WOW 神奇地改变了一切。
底线是:正确的基准测试确实很难,并且需要了解 JIT 如何编译、GC 何时可能成为问题(删除它或接受它)等等。
链接: