切换似乎比如果慢

我很好奇切switch速度,认为它“非常”快,但我有一个测试用例似乎显示单个开关大约快4, if测试,当我预期(没有坚实的理由)它与1次测试一样快。 这是我用来比较switch的两种方法if

 public static int useSwitch(int i) { switch (i) { case 1: return 2; case 2: return 3; case 3: return 4; case 4: return 5; case 5: return 6; case 6: return 7; default: return 0; } } public static int useIf(int i) { if (i == 1) return 2; if (i == 2) return 3; if (i == 3) return 4; if (i == 4) return 5; if (i == 5) return 6; if (i == 6) return 7; return 0; } 

这是我的测试代码:

 long x = 0; for (int i = 0; i < 999999; i++) x += useIf(i % 7); // I use "x" so calls won't get optimized out 

和另一个相同的循环useSwitch()

在我的机器上,这些循环大约需要相同的时间才能完成,这是一个惊喜。
我得到ifs的数量为“4”,因为这是给定输入范围的平均值(我认为)。
如果我减少逻辑选项的数量, if版本明显更快。

我的问题是:

switch实际上不是那么快,还是以某种方式进行“不公平”测试?

这在某种程度上是不公平的比较。 大部分CPU时间将用于处理模运算: i % 7 。 甚至在最新的最大CPU上的模数也非常慢,并且执行的时间可能比你试图进行基准测试的if()或switch()实现的时间长20倍。

此外,有两种不同的方式可以优化switch语句。 一个是查找表,用于顺序情况,就像你提出的那样。 另一种方法是在适当的地方使用搜索分区树 。 当案例稀疏时会发生这种情况,例如在以下示例中:

 switch (someInt) { case 0: ... break; case 10: ... break; case 102: ... break; case 6543: ... break; case 19303: ... break; case 19305: ... break; // and so forth... } 

大多数编译器将使用展开的分区树来查找正确的大小写,在长开关上可以提供非常好的平均值和最坏情况下的跳转来击中正确的大小写。 生成的伪代码将是这样的:

 if (someInt >= 6543) { if (someInt >= 19303) { // continue tree search, etc. } else if (someInt==6543) {} } else if (someInt >= 0) { if (someInt >= 10) { // continue tree search, etc. } else if (someInt == 0) {} } else { // default case handler... } 

如图所示,它只有6-8个案例并没有多大帮助,但是如果你有一个可能超过50个案例的开关那么它真的很有用。 线性搜索将具有O(n)(50个条件最差,25个平均),而分区树版本将接近sqrt(n)(8-9条件最差,5-7个平均值,取决于编译器选择)。

切换应该更快,它足以查看字节码

  TABLESWITCH 1: L1 2: L2 3: L3 4: L4 5: L5 6: L6 

看到这是一项特殊的操作。 在现实生活中,由于JVM优化,可能没有区别。 但是如果我们使用-Xint运行你的代码(仅限互联网模式),那么差异应该很明显,在我的电脑上,63到93(ms)支持切换。

这里有趣的问题是编译器如何转换switch(可能类似于哈希表,或者结果可能类似于if-else结构)。 它还可以根据输入进行各种优化(如果已知)。 但是,这是依赖于编译器的,并且我没有在任何我知道的规范中定义。

所以,我不能给你答案,但我可以告诉你,如果你想进一步优化你的测试,你总是可以给它一个输入7.这将使它通过所有测试直到返回答案,如果开关经过优化,它会给你更好的结果,如果它就像if-else那样会有类似的结果。 只是不要硬编码7,以便编译器不提前知道,使用类似parseint的东西从字符串中获取它。

由于您的代码可能会或可能不会根据JIT的智能消除,您可能已经确定if语句比交换机更容易消除。 这并不奇怪,因为许多if语句更有可能被消除,例如断言和调试检查。