Как оптимизировать оператор вида "if (A == B) { ...} else if (A <B) {...} else {....}"

У меня есть кусок кода, который похож

if (A == B)
{
    ...
}
else if (A < B)
{
    ... 
}
else // (A > B)
{
    ...
}

Я понимаю, что есть проблема избыточности, потому что будут некоторые из тех же самых битовых сравнений, идущих в вычислении == а также <, Как я могу оптимизировать свой код, чтобы сделать его более интересным и быстрым?

2 ответа

Решение

Для C# вы можете использовать универсальную функцию, которая принимает 2 значения, а затем лямбда-действие для каждого случая.

void CompareAndAct<T>(T a, T b, Action fnEqualTo, Action fnLessThan, Action fnGreaterThan)  {

   var comparison = System.Collections.Generic.Comparer<T>.Default.Compare(a, b);
   if (comparison == 0) {
      fnEqualTo();
   }
   else if (comparison < 0) {
      fnLessThan();
   }
   else {  //A > B
      fnGreaterThan();
   }
}

Затем вы можете использовать его столько раз, сколько захотите:

CompareAndAct(a,b, () => Console.Writeline("Equal"),() => Console.WriteLine("Less Than", () => Console.WriteLine("Greater Than"));

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

Вы не указываете язык, но в зависимости от языка это может быть переписано разными способами

Рубиновый способ (с использованием оператора космического корабля):

case A <=> B
    when -1 then... # A < B
    when  0 then... # A = B
    when  1 then... # A > B
end

Perl, PHP7 и Groovy также имеют один и тот же оператор. Многие другие языки имеют аналогичные операторы или функции для той же цели комбинированного сравнения, как cmp в Python 2, compare в OCaml и compareTo в Котлине. C# не имеет этого оператора, но имеет IComparable интерфейс с CompareTo метод.

VB путь:

Select Case A
    Case Is < B
        ...
    Case Is = B
        ...
    Case Is > B
        ...
End Select

В C, C++ и многих C-подобных языках без CompareTo метод, которым вы можете использовать этот способ

int cmp = (A > B) - (A < B);
switch (cmp)
{
    case -1: ...
    case  0: ...
    case  1; ...
}

Многие языки, такие как Java, не позволяют напрямую использовать результаты сравнения в виде числового значения. В этом случае вы можете использовать signum функция

switch(Integer.signum(A - B))

Вы можете реализовать signum легко функционировать в C и C++, как это

Это для языков высокого уровня. На уровне сборки все проще. В сборке x86 требуется только одно сравнение, тогда в зависимости от результата мы перейдем к соответствующему блоку, так что это не 3 сравнения, а компилятор достаточно умен, чтобы оптимизировать этот простой случай. Например:

    cmp eax, ebx
    je EQUAL_TO      ; jump if =
    ja GREATER_THAN  ; jump if >

    ; less than case's code
    jmp END_CMP

EQUAL_TO:
    ; equal case's code
    jmp END_CMP

GREATER_THAN:
    ; larger than case's code

END_CMP:

То же самое с другими архитектурами с флагами сравнения, такими как ARM или 68k... Для архитектур без флага, такого как MIPS, вам может потребоваться еще одно сравнение, но не 3 сравнения

Пример MIPS:

    beq $t0, $t1, EQUAL_TO       # $t0 = A, $t1 = B; if ($t0 == $t1) equal();
    slt $t0, $t1, $t2            # $t2 = is_less_than = ($t0 < $t1);
    beq $t2, $zero, GREATER_THAN # if (!is_less_than) larger();

    # "less than" code here
    # ...
    j END_CMP

EQUAL_TO:
    # "equal" code
    # ...
    j END_CMP

GREATER_THAN:
    # "larger" code
    # ...

END_CMP:

Для архитектур с условными инструкциями, такими как ARM или Itanium и с достаточно простым телом в блоках if-else, вам может даже не потребоваться переход

Другие вопросы по тегам