【问题标题】:Poor performance of many if-else statements in JavaJava 中许多 if-else 语句的性能不佳
【发布时间】: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 实例吗?

标签: java performance if-statement conditional


【解决方案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 中使用。

            【讨论】:

              猜你喜欢
              • 2017-01-22
              • 1970-01-01
              • 2023-03-28
              • 1970-01-01
              • 2014-10-29
              • 1970-01-01
              • 2014-01-07
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多