Низкая производительность многих операторов if-else в Java

у меня есть метод, который проверяет все комбинации 5 различных условий с 32 утверждениями if-else (подумайте о таблице истинности). 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();

спасибо!

10 ответов


вы можете попробовать

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;
}

представляют каждое условие как битовый флаг, проверяют каждое условие один раз и устанавливают соответствующий флаг в одном 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)
  // ...
}

Не зная более подробной информации, было бы полезно организовать операторы if таким образом, чтобы те, которые выполняют "тяжелый" подъем, выполнялись последними. Это делает предположение, что другие условия будут истинными, тем самым избегая "тяжелых" подъемных всех вместе. Короче говоря, воспользуйтесь преимуществами короткого замыкания, если это возможно.


все вышеперечисленные ответы неверны, потому что правильный ответ на вопрос оптимизации: мера! использование профайлера, чтобы определить, где ваш код тратит свое время.

сказав это, я был бы готов поспорить, что самая большая победа-избежать компиляции регулярных выражений более одного раза. И после этого, как предлагали другие, оценивайте каждое условие только один раз и сохраняйте результаты в булевых переменных. Так thait84 имеет лучший ответ.

Я также готов поспорить, что предложения jtahlborn и Peter Lawrey's и Salvatore Previti (по сути, те же), хотя и умные, дадут вам незначительную дополнительную выгоду, если вы не работаете на 6502...

(этот ответ читается так, как будто я полон его, поэтому в интересах полного раскрытия я должен упомянуть, что я на самом деле безнадежен в оптимизации. Но измерение все еще является правильным ответом.)


запустите регулярное выражение один раз для каждой строки и сохраните результаты в booleans и просто выполните if / else на booleans вместо запуска регулярного выражения несколько раз. Кроме того, если вы можете, попробуйте повторно использовать предварительно скомпилированную версию вашего регулярного выражения и повторно использовать это.


одно возможное решение: используйте переключатель, создающий двоичное значение.

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:
}

Если вы можете избежать коммутатора и использовать массив, это будет быстрее.


возможно, разбить его на слои, например:

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

тем не менее, похоже, что должен быть лучший способ сделать это.


у меня есть решение с EnumSet. Однако это слишком многословно, и я думаю, что предпочитаю решение @Peter Lawrey.

В эффективная 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
}

вы также можете адаптировать свой if / else к коммутатору / case (который, как я понимаю, быстрее)


предварительно генерируя A,B,C, D и E как булевы, а не оценивая их в if условия блоков даст читаемость и производительность. Если вы также обеспокоены производительностью различных случаев, вы можете организовать их как дерево или объединить их в одно целое число (X = (a?1: 0) / (B?2:0)|...(E?16: 0)) который вы бы использовали в switch.