Java 中许多 if-else 语句的性能不佳

Posted

技术标签:

【中文标题】Java 中许多 if-else 语句的性能不佳【英文标题】:Poor performance of many if-else statements in Java 【发布时间】:2011-12-15 01:01:24 【问题描述】:

我有一个方法可以用 32 个 if-else 语句检查 5 个不同条件的所有组合(想想真值表)。 5 个不同的字母代表方法,每个方法在字符串上运行自己的正则表达式,并返回一个布尔值,指示字符串是否与正则表达式匹配。例如:

if(A,B,C,D,E)

else if(A,B,C,D,!E)

else if(A,B,C,!D,!E)

...etc,etc.

但是,它确实影响了我的应用程序的性能(抱歉,我无法详细说明)。谁能推荐一种更好的方法来处理这种逻辑?

每个使用正则表达式的方法如下所示:

String re1 = "regex here";
Pattern p = Pattern.compile(re1, Pattern.DOTALL);
Matcher m = p.matcher(value);
return m.find();

谢谢!

【问题讨论】:

如果您向我们展示伪代码以外的其他内容会有所帮助...例如,您是否每次都实际评估所有条件,或者您是否评估一次并且只检查组合? 我添加了如何处理每种方法的正则表达式,这有帮助吗? 我敢打赌 A、B、C... 都是它们需要评估的重要文件... 将结果保存在本地布尔值中。编辑(用于编辑),因此您评估正则表达式(每次/多次启动都很慢) @littleK:不是真的 - 因为你的 if 代码仍然不是有效的 Java... 你能在方法之间共享 Pattern 实例吗? 【参考方案1】:

你可以试试

boolean a,b,c,d,e;
int combination = (a?16:0) + (b?8:0) + (c?4:0) + (d?2:0) + (e?1:0);
switch(combination) 
   case 0:
        break;
   // through to
   case 31:
        break;

【讨论】:

这个解决方案对我来说效果很好,并且确实提高了性能。我应该从一开始就意识到我的设计很糟糕!再次感谢。 提醒:从本质上讲,这仍然是一系列“if / else”语句。它最终变得更快,因为编译器会将其优化为“跳转表”。请参阅en.wikipedia.org/wiki/Switch_statement#Compilation 和相关链接(“分支表”)以进一步了解如何在恒定时间内完成 switch 语句。 @JeffFerland '本机这仍然是一系列“if/else”语句是没有意义的。 “本机”,如果这意味着什么,就是它编译成的东西,正如你继续正确地说的那样,它是一个分支表。 @EJP 没有编译器优化,因为当使用不是整数/枚举的东西时,或者如果编译器不喜欢你,它将是一系列测试而不是跳转。 @JeffFerland 一开始就依赖于编译器,但无论如何,这距离“原生它仍然是一系列 if/else 语句”还有很长的路要走。尤其是所讨论的语言是 Java。【参考方案2】:

将每个条件表示为一个位标志,测试每个条件一次,并将相关标志设置在单个 int 中。然后打开 int 值。

int result = 0;
if(A) 
  result |= 1;

if(B) 
  result |= 2;

// ...

switch(result) 
  case 0: // (!A,!B,!C,!D,!E)
  case 1: // (A,!B,!C,!D,!E)
  // ...

【讨论】:

【参考方案3】:

以上所有答案都是错误的,因为优化问题的正确答案是:测量!使用分析器来测量您的代码在哪里花费时间。

话虽如此,我敢打赌,最大的胜利是避免每次编译正则表达式超过一次。之后,正如其他人所建议的那样,只评估每个条件一次并将结果存储在布尔变量中。所以 thait84 有最好的答案。

我还准备打赌 jtahlborn 和 Peter Lawrey 和 Salvatore Previti 的建议(基本上相同),尽管它们很聪明,但会给你带来微不足道的额外好处,除非你在 6502 上运行......

(这个答案读起来好像我已经满了,所以为了充分披露,我应该提到我实际上在优化方面没有希望。但测量仍然是正确的答案。)

【讨论】:

“以上所有答案都是错误的,因为优化问题的正确答案是:测量!” 您可以将其应用于任何事情。当提出问题的人正在寻找另一种测量方法时,我会说它不适用...... 什么,你会说它不适用于在尝试纠正之前测量性能问题?你确定吗? 经过反思,OP 确实明确要求改进他的代码,但我没有回答。也许我应该评论他的问题。【参考方案4】:

在不了解更多细节的情况下,安排 if 语句以使执行“繁重”提升的语句最后执行可能会有所帮助。这是假设其他条件将是真实的,从而避免了“繁重”的提升。简而言之,尽可能利用短路。

【讨论】:

我确实打算使用您的建议。我很想看看我能看到什么样的性能提升。不过,我认为我需要做更多的事情。【参考方案5】:

为每个字符串运行一次正则表达式并将结果存储到布尔值中,然后对布尔值执行 if / else,而不是多次运行正则表达式。此外,如果可以,请尝试重新使用正则表达式的预编译版本并重新使用它。

【讨论】:

【参考方案6】:

一种可能的解决方案:使用开关创建二进制值。

int value = (a ? 1 : 0) | (b ? 2 : 0) | (c ? 4 : 0) | (d ? 8 : 0) | (e ? 16 : 0);

switch (value)

    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    ...
    case 31:

如果你可以避免切换并使用数组会更快。

【讨论】:

【参考方案7】:

也许把它分成几层,像这样:

if(A) 
    if(B) 
        //... the rest
     else 
        //... the rest
    
 else 
    if(B) 
        //... the rest
     else 
        //... the rest
    

不过,感觉必须有更好的方法来做到这一点。

【讨论】:

【参考方案8】:

我有一个 EnumSet 的解决方案。但是它太冗长了,我想我更喜欢@Peter Lawrey 的解决方案。

在 Bloch 的 Effective Java 中,建议在位字段上使用 EnumSet,但在这里我会例外。尽管如此,我还是发布了我的解决方案,因为它可能对有稍微不同问题的人有用。

import java.util.EnumSet;

public enum MatchingRegex 
  Tall, Blue, Hairy;

  public static EnumSet<MatchingRegex> findValidConditions(String stringToMatch) 
     EnumSet<MatchingRegex> validConditions = EnumSet.noneOf(MatchingRegex.class);
     if (... check regex stringToMatch for Tall)
       validConditions.add(Tall);
     if (... check regex stringToMatch for Blue)
       validConditions.add(Blue);
     if (... check regex stringToMatch for Hairy)
       validConditions.add(Hairy);
     return validConditions;         
  

然后你像这样使用它:

Set<MatchingRegex> validConditions = MatchingRegex.findValidConditions(stringToMatch);

if (validConditions.equals(EnumSet.of(MatchingRegex.Tall, MathchingRegex.Blue, MatchingRegex.Hairy))
   ...
else if (validConditions.equals(EnumSet.of(MatchingRegex.Tall, MathchingRegex.Blue))
   ...
else if ... all 8 conditions like this

但这样会更有效率:

if (validConditions.contains(MatchingRegex.Tall)) 
  if (validConditions.contains(MatchingRegex.Blue)) 
     if (validConditions.contains(MatchingRegex.Hairy)) 
        ... // tall blue hairy
     else
        ... // tall blue (not hairy)
   else 
     if (validConditions.contains(MatchingRegex.Hairy)) 
        ... // tall (not blue) hairy
     else
        ... // tall (not blue) (not hairy)
 else 
      ... remaining 4 conditions

【讨论】:

+1 我觉得这个方案更容易理解和修改(不会出错) 通过将EnumSet 替换为四个布尔值,这肯定会变得更短、更易读、更快。 -1 用于使用它们,+1 用于使用嵌套条件(可能与 switch 一样快并且更具可读性)。【参考方案9】:

您还可以将您的 if/else 调整为 switch/case(据我所知更快)

【讨论】:

【参考方案10】:

将 A、B、C、D 和 E 预先生成为布尔值,而不是在 if 条件块中评估它们将提供可读性和性能。如果您还关心不同情况的性能,您可以将它们组织为一棵树或将它们组合成一个整数 (X = (A?1:0)|(B?2:0)|...|( E?16:0)) 在switch 中使用。

【讨论】:

以上是关于Java 中许多 if-else 语句的性能不佳的主要内容,如果未能解决你的问题,请参考以下文章

使用 nHibernate 选择具有许多子集合的实体的性能不佳

MySQL 性能不佳 INSERT 或 PHP PDO

“全栈2019”Java第二十二章:控制流程语句中的决策语句if-else

SQL Server 查询性能不佳

Java中条件语句和if-else的嵌套原则

For 循环首先打印 if-else 语句的错误分支