java编译器有多聪明?关于枚举方法的几个问题
考虑这种情况: 样式1: 样式2:java编译器有多聪明?关于枚举方法的几个问题,java,enums,compiler-construction,Java,Enums,Compiler Construction,考虑这种情况: 样式1: 样式2: static enum Style2{ FIRE_BALL,ICE_BALL,FIRE_ARROW,ICE_ARROW; public boolean isCold(){ //return this.toString().contains("ICE")?true:false; //sorry return this.toString().contains("ICE"); } } 现在,我只想知道天气是
static enum Style2{
FIRE_BALL,ICE_BALL,FIRE_ARROW,ICE_ARROW;
public boolean isCold(){
//return this.toString().contains("ICE")?true:false; //sorry
return this.toString().contains("ICE");
}
}
现在,我只想知道天气是否冷。所以我要问:
编译器能否知道最终结果和常量折叠样式2?
如果不是,Style1应该明显更快,但更详细。假设这是一个更复杂的情况,并且有更多的组合,例如BIG_FIRE_SLOW_BALL和isFast()、isBig()、Style1将以代码块结束
所以我用jmh和jUnit做了一些测试: 1.jmh:
@Benchmark
public boolean testStyle1() {
return Style1.values()[ThreadLocalRandom.current().nextInt(4)].isCold();
}
@Benchmark
public boolean testStyle2() {
return Style2.values()[ThreadLocalRandom.current().nextInt(4)].isCold();
}
设置时:
.warmupIterations(10)
.measurementIterations(10)
.threads(8)
Benchmark Mode Cnt Score Error Units
EnumTest1.testStyle1 avgt 10 34.057 ± 0.101 ns/op
EnumTest1.testStyle2 avgt 10 36.196 ± 0.453 ns/op
那么,将线程数设置为1
.threads(1)
Benchmark Mode Cnt Score Error Units
EnumTest1.testStyle1 avgt 10 34.306 ± 11.692 ns/op
EnumTest1.testStyle2 avgt 10 44.279 ± 11.103 ns/op
因此,编译器似乎无法优化样式2。
2、使用jUnit:
private static final int LOOP_TIMES = 100000000;
private static final Random random1=new Random(47);
private static final Random random2=new Random(47);
@Test
public void testStyle1() {
int cnt = 0;
for (int i = 0; i < LOOP_TIMES; i++) {
if(Style1.values()[random1.nextInt(4)].isCold()){
cnt++;
}
}
System.out.println(cnt);
}
@Test
public void testStyle2() {
int cnt = 0;
for (int i = 0; i < LOOP_TIMES; i++) {
if(Style2.values()[random2.nextInt(4)].isCold()){
cnt++;
}
}
System.out.println(cnt);
}
因此,Style1可能会更快
但是为什么两个结果非常接近,特别是当我同时使用jmh进行测试时?或者我们究竟应该如何处理这一问题? 也许给Style1一些字段来存储每个结果可以减少冗余。但我还是觉得不太满意。希望你们中的一些人能告诉我更多
非常感谢你们@安迪举了一个很好的例子,我在这里补充:
enum Style4{
FIRE_BALL,
ICE_BALL,
FIRE_ARROW,
ICE_ARROW;
private boolean cold;
private Style4(){
this.cold = this.toString().contains("ICE");
}
public boolean isCold(){
return cold;
}
}
第四种样式在工作时没有提到true或false。如果使用构造函数,可以在某种程度上改进样式1的详细性。这将是快速的,而且(在我看来)更容易阅读
enum Style1{
FIRE_BALL(false),
ICE_BALL(true),
FIRE_ARROW(false),
ICE_ARROW(true);
private final cold;
private Style1(boolean cold){
this.cold = cold;
}
public boolean isCold(){
return cold;
}
}
请注意,这三种样式都不可能成为代码中的热点。更重要的是编写更易于阅读的代码,并在以后根据需要对性能进行调整。如果使用构造函数,可以在某种程度上改进style1的冗长性。这将是快速的,而且(在我看来)更容易阅读
enum Style1{
FIRE_BALL(false),
ICE_BALL(true),
FIRE_ARROW(false),
ICE_ARROW(true);
private final cold;
private Style1(boolean cold){
this.cold = cold;
}
public boolean isCold(){
return cold;
}
}
请注意,这三种样式都不可能成为代码中的热点。更重要的是编写更易于阅读的代码,并在以后需要时对性能进行调整
编译器能否知道最终结果和常量折叠样式2
没有
但是为什么两个结果很接近,特别是当我同时使用jmh进行测试时?或者我们究竟应该如何处理
正如@JB Nizet所说,基准测试方法中的大部分时间都花在查找ThreadLocalRandom
实例和生成下一个随机数上
我不确定这一点,但我认为values()
调用必须在每次调用时创建一个新数组。这在一定程度上可以解释为什么你在时间安排上会有很多变化。(JLS对此没有具体说明,但如果values()
没有每次返回新数组,则不受信任的代码可以更新数组并导致受信任的代码出现问题。)
编译器能否知道最终结果和常量折叠样式2
没有
但是为什么两个结果很接近,特别是当我同时使用jmh进行测试时?或者我们究竟应该如何处理
正如@JB Nizet所说,基准测试方法中的大部分时间都花在查找ThreadLocalRandom
实例和生成下一个随机数上
我不确定这一点,但我认为values()
调用必须在每次调用时创建一个新数组。这在一定程度上可以解释为什么你在时间安排上会有很多变化。(JLS对此没有具体说明,但如果values()
没有每次返回新数组,则不受信任的代码可以更新数组并导致受信任的代码出现问题。)
但是为什么两个结果很接近,特别是当我同时使用jmh进行测试时?或者我们究竟应该如何处理
您真正关心的代码是这样一种方法
return true;
但是,您要添加到这一点
if(Style2.values()[random2.nextInt(4)].isCold()){
cnt++;
}
方法Style2.value()
是一个昂贵的方法,它返回类型Style2
方法nextInt(4)
相当便宜,但包括相对昂贵的%
您还有一个if
条件,这取决于您拥有的Java版本,因为它会导致分支未命中。较新版本的Java使用CMOV
指令,这一指令处理得更好
简言之,结果与您花费大部分时间做的工作基本相同,也就是说,您认为您正在测试的代码并不相同
但是为什么两个结果很接近,特别是当我同时使用jmh进行测试时?或者我们究竟应该如何处理
您真正关心的代码是这样一种方法
return true;
但是,您要添加到这一点
if(Style2.values()[random2.nextInt(4)].isCold()){
cnt++;
}
方法Style2.value()
是一个昂贵的方法,它返回类型Style2
方法nextInt(4)
相当便宜,但包括相对昂贵的%
您还有一个if
条件,这取决于您拥有的Java版本,因为它会导致分支未命中。较新版本的Java使用CMOV
指令,这一指令处理得更好
简言之,结果与您花费大部分时间做的工作基本相同,也就是说,您认为您正在测试的代码中没有测试结果。您的基准测试主要测试生成随机数的时间。这两种方法都足够快,在实际应用程序中,这永远不会导致任何性能问题。因此,请使用您认为最具可读性和可维护性的。您可以添加第三种方法:在每个枚举的构造函数中初始化一个布尔实例变量,并由一个通用的isCold()方法返回。我可以建议吗?第四种方法,基于第三种方法:不要传递构造函数参数,只需在ctor中计算
toString().contains(“ICE”)
,然后分配给一个字段。Nit:this.toString().contains(“ICE”)?真:假
为e