Условный оператор является медленным?

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

  1. Исходный код похож на это:

    public static bool SwitchIfElse(Key inKey, out char key, bool shift)
    {
        switch (inKey)
        {
           case Key.A: if (shift) { key = 'A'; } else { key = 'a'; } return true;
           case Key.B: if (shift) { key = 'B'; } else { key = 'b'; } return true;
           case Key.C: if (shift) { key = 'C'; } else { key = 'c'; } return true;
           ...
           case Key.Y: if (shift) { key = 'Y'; } else { key = 'y'; } return true;
           case Key.Z: if (shift) { key = 'Z'; } else { key = 'z'; } return true;
           ...
           //some more cases with special keys...
        }
        key = (char)0;
        return false;
    }
    
  2. Второй вариант, преобразованный для использования условного оператора:

    public static bool SwitchConditionalOperator(Key inKey, out char key, bool shift)
    {
        switch (inKey)
        {
           case Key.A: key = shift ? 'A' : 'a'; return true;
           case Key.B: key = shift ? 'B' : 'b'; return true;
           case Key.C: key = shift ? 'C' : 'c'; return true;
           ...
           case Key.Y: key = shift ? 'Y' : 'y'; return true;
           case Key.Z: key = shift ? 'Z' : 'z'; return true;
           ...
           //some more cases with special keys...
        }
        key = (char)0;
        return false;
    }
    
  3. Скручивание с помощью словаря, предварительно заполненного ключевыми/символьными парами:

    public static bool DictionaryLookup(Key inKey, out char key, bool shift)
    {
        key = '\0';
        if (shift)
            return _upperKeys.TryGetValue(inKey, out key);
        else
            return _lowerKeys.TryGetValue(inKey, out key);
    }
    

Примечание: эти два оператора переключения имеют те же самые случаи, и словари имеют равное количество символов.

Я ожидал, что 2) 1) и 2) было несколько подобно в производительности и который 3) будет немного медленнее.

Для каждого метода, выполняющего два раза 10 000 000 повторений для прогрева и затем синхронизированный, к моему изумлению, я получаю следующие результаты:

  1. 0,0000166 миллисекунды на вызов
  2. 0,0000779 миллисекунды на вызов
  3. 0,0000413 миллисекунды на вызов

Как это может быть? Условный оператор в четыре раза медленнее, чем если еще операторы и почти в два раза медленнее, чем поиски по словарю. Я пропускаю что-то существенное здесь, или условный оператор является по сути медленным?

Обновление 1: Несколько слов о моей тестовой обвязке. Я работаю, следующий (псевдо) код для каждого из вышеупомянутых вариантов при Выпуске скомпилировал проект.Net 3.5 в Visual Studio 2010. Оптимизация кода включена, и константы ОТЛАДКИ/ТРАССИРОВКИ выключены. Я выполняю метод при измерении однажды для прогрева прежде, чем сделать синхронизированное выполнение. Метод выполнения выполнил метод для большого количества повторений, с shift набор и к истине и лжи и с избранным набором входных ключей:

Run(method);
var stopwatch = Stopwatch.StartNew();
Run(method);
stopwatch.Stop();
var measure = stopwatch.ElapsedMilliseconds / iterations;

Метод Выполнения похож на это:

for (int i = 0; i < iterations / 4; i++)
{
    method(Key.Space, key, true);
    method(Key.A, key, true);
    method(Key.Space, key, false);
    method(Key.A, key, false);
}

Обновление 2: Роя далее, я посмотрел на IL, сгенерированный для 2) 1) и 2) и находят, что структуры главного выключателя идентичны, как я ожидал бы, все же тела случая имеют незначительные различия. Вот IL, на который я смотрю:

1) Если/еще оператор:

L_0167: ldarg.2 
L_0168: brfalse.s L_0170

L_016a: ldarg.1 
L_016b: ldc.i4.s 0x42
L_016d: stind.i2 
L_016e: br.s L_0174

L_0170: ldarg.1 
L_0171: ldc.i4.s 0x62
L_0173: stind.i2 

L_0174: ldc.i4.1 
L_0175: ret 

2) Условный оператор:

L_0165: ldarg.1 
L_0166: ldarg.2 
L_0167: brtrue.s L_016d

L_0169: ldc.i4.s 0x62
L_016b: br.s L_016f

L_016d: ldc.i4.s 0x42
L_016f: stind.i2 

L_0170: ldc.i4.1 
L_0171: ret 

Некоторые наблюдения:

  • Условный оператор переходит когда shift равняется верный в то время как если/еще ответвления когда shift ложь.
  • В то время как 1) на самом деле компилирует еще в несколько инструкций, чем 2), количество инструкций, выполняемых когда shift любой TRUE или FALSE, равны для двух.
  • Упорядочивание инструкции для 1) таково, что только один слот стека занят в любом случае, в то время как 2) всегда загружается два.

Какое-либо из этих наблюдений подразумевает, что условный оператор будет работать медленнее? Есть ли другие побочные эффекты, которые играют роль?

25
задан Peter Lillevold 11 March 2014 в 20:02
поделиться

8 ответов

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

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

http://dotnetperls.com/ternary

Вы могли бы рассмотреть ToString на значении перечисления (для неспециальных случаев):

string keyValue = inKey.ToString();
return shift ? keyValue : keyValue.ToLower();

EDIT:
Я сравнил метод if-else с тернарным оператором и при 1000000 циклах тернарный оператор всегда по крайней мере так же быстр, как метод if-else (иногда на несколько миллисекунд быстрее, что подтверждает текст выше). Я думаю, что вы допустили какую-то ошибку при измерении времени, которое потребовалось.

12
ответ дан 28 November 2019 в 21:37
поделиться

Я не совсем понимаю, почему вы ожидаете, что оператор if будет медленнее, чем поиск по словарю. По крайней мере, необходимо вычислить хэш-код, а затем его нужно найти в списке. Я не понимаю, почему вы думаете, что это быстрее, чем cmp / jmp.

В частности, я даже не думаю, что метод, который вы оптимизируете, настолько хорош; кажется, что это можно было бы улучшить на этапе вызова (хотя я не уверен, так как вы не предоставили контекст).

2
ответ дан 28 November 2019 в 21:37
поделиться

Используется для указания атрибута keyIdentifier объекта .

<html>
<head>

<script type="text/javascript">
  document.onkeydown = function() {
  alert (event.keyIdentifier);
}; 
</script>
</head>
<body>
</body>
</html>

Тогда вы можете использовать логику if, чтобы игнорировать все клавиши, которые вас не интересуют, и направить правильное поведение к тем, кто вы есть.

Ниже приведены ссылки со стрелками влево и вправо (на основе идентификатора элементов привязки/ссылки).

<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>

<script type="text/javascript">
    $(document).ready(function() {
            document.onkeydown = function() 
                {
                    var j = event.keyIdentifier
                    if (j == "Right")
                        window.location = nextUrl
                    else if (j == "Left")
                        window.location = prevUrl            
                        }
                   });

      $(document).ready(function() {
                    var nextPage = $("#next_page_link")
                    var prevPage = $("#previous_page_link")
                    nextUrl = nextPage.attr("href")
                    prevUrl = prevPage.attr("href")
                });

</script>
</head>
<body>
<p>
    <a id="previous_page_link" href="http://www.google.com">Google</a> 
    <a id="next_page_link" href="http://www.yahoo.com">Yahoo</a>
</p>
</body>
</html>
-121--2655729-

Создание экземпляра шаблона с последовательностью для использования в качестве шаблона.

-121--237724-

Я бы ожидал, что # 1 и # 2 будут одинаковыми. Оптимизатор должен иметь тот же код. Словарь в # 3 должен быть медленным, если он не оптимизирован так или иначе, чтобы не использовать хэш.

При кодировании систем реального времени мы всегда использовали таблицу поиска - простой массив - для перевода, как указано в вашем примере. Это самое быстрое, когда диапазон входных данных довольно мал.

3
ответ дан 28 November 2019 в 21:37
поделиться

Создание экземпляра шаблона с последовательностью для использования в качестве шаблона.

-121--237724-

Я бы ожидал, что # 1 и # 2 будут одинаковыми. Оптимизатор должен иметь тот же код. Словарь в # 3 должен быть медленным, если он не оптимизирован так или иначе, чтобы не использовать хэш.

При кодировании систем реального времени мы всегда использовали таблицу поиска - простой массив - для перевода, как указано в вашем примере. Это самое быстрое, когда диапазон входных данных довольно мал.

-121--1496344-

Я бы выбрал третий вариант только потому, что он более удобочитаемый/ремонтопригодный. Спорим, этот код не является узким местом в производительности вашего приложения.

-1
ответ дан 28 November 2019 в 21:37
поделиться

Мне было бы любопытно узнать, тестируете ли вы это с помощью сборки отладки или выпуска. Если это отладочная сборка, то разница, скорее всего, может быть различием из-за ОТСУТСТВИЯ низкоуровневых оптимизаций, которые компилятор добавляет, когда вы используете режим выпуска (или вручную отключите режим отладки и включите оптимизацию компилятора)

I Однако при включенной оптимизации можно ожидать, что тернарный оператор будет иметь ту же скорость или немного быстрее, чем оператор if / else, в то время как поиск по словарю будет самым медленным. Вот мои результаты, 10 миллионов итераций прогрева, за которыми следуют 10 миллионов синхронизированных по времени, для каждой:

РЕЖИМ ОТЛАДКИ

   If/Else: 00:00:00.7211259
   Ternary: 00:00:00.7923924
Dictionary: 00:00:02.3319567

РЕЖИМ ВЫПУСКА

   If/Else: 00:00:00.5217478
   Ternary: 00:00:00.5050474
Dictionary: 00:00:02.7389423

Я думаю, что здесь интересно отметить, что до включения оптимизаций троичные вычисления медленнее, чем if / else, а после - быстрее.

РЕДАКТИРОВАТЬ:

После небольшого дополнительного тестирования, в практическом смысле, нет почти никакой разницы между if / else и тернарным. Хотя троичный код приводит к уменьшению IL, они работают почти так же, как друг друга. В десятке различных тестов с двоичным кодом режима выпуска результаты if / else и троичные были либо идентичны, либо отклонялись на доли миллисекунды для 10 000 000 итераций. Иногда if / else было немного быстрее, иногда было тройным, но с практической точки зрения они работают одинаково.

С другой стороны, словарь работает значительно хуже. Когда дело доходит до таких оптимизаций, я бы не стал тратить время на выбор между if / else и тернарным, если код уже существует.Однако, если у вас в настоящее время есть реализация словаря, я бы определенно реорганизовал ее, чтобы использовать более эффективный подход и улучшить вашу производительность примерно на 400% (во всяком случае, для данной функции).

11
ответ дан 28 November 2019 в 21:37
поделиться

Интересно, я пошел и разработал небольшой класс IfElseTernaryTest здесь, хорошо, код на самом деле не «оптимизирован» и не является хорошим примером, но тем не менее ... ради обсуждения:

public class IfElseTernaryTest
{
    private bool bigX;
    public void RunIfElse()
    {
        int x = 4; int y = 5;
        if (x &gt; y) bigX = false;
        else if (x &lt; y) bigX = true; 
    }
    public void RunTernary()
    {
        int x = 4; int y = 5;
        bigX = (x &gt; y) ? false : ((x &lt; y) ? true : false);
    }
}

Это был дамп кода IL ... интересная часть заключалось в том, что тернарные инструкции в IL были на самом деле короче, чем if ....

.class /*02000003*/ public auto ansi beforefieldinit ConTern.IfElseTernaryTest
       extends [mscorlib/*23000001*/]System.Object/*01000001*/
{
  .field /*04000001*/ private bool bigX
  .method /*06000003*/ public hidebysig instance void 
          RunIfElse() cil managed
  // SIG: 20 00 01
  {
    // Method begins at RVA 0x205c
    // Code size       44 (0x2c)
    .maxstack  2
    .locals /*11000001*/ init ([0] int32 x,
             [1] int32 y,
             [2] bool CS$4$0000)
    .line 19,19 : 9,10 ''
//000013:     }
//000014: 
//000015:     public class IfElseTernaryTest
//000016:     {
//000017:         private bool bigX;
//000018:         public void RunIfElse()
//000019:         {
    IL_0000:  /* 00   |                  */ nop
    .line 20,20 : 13,23 ''
//000020:             int x = 4; int y = 5;
    IL_0001:  /* 1A   |                  */ ldc.i4.4
    IL_0002:  /* 0A   |                  */ stloc.0
    .line 20,20 : 24,34 ''
    IL_0003:  /* 1B   |                  */ ldc.i4.5
    IL_0004:  /* 0B   |                  */ stloc.1
    .line 21,21 : 13,23 ''
//000021:             if (x &gt; y) bigX = false;
    IL_0005:  /* 06   |                  */ ldloc.0
    IL_0006:  /* 07   |                  */ ldloc.1
    IL_0007:  /* FE02 |                  */ cgt
    IL_0009:  /* 16   |                  */ ldc.i4.0
    IL_000a:  /* FE01 |                  */ ceq
    IL_000c:  /* 0C   |                  */ stloc.2
    IL_000d:  /* 08   |                  */ ldloc.2
    IL_000e:  /* 2D   | 09               */ brtrue.s   IL_0019

    .line 21,21 : 24,37 ''
    IL_0010:  /* 02   |                  */ ldarg.0
    IL_0011:  /* 16   |                  */ ldc.i4.0
    IL_0012:  /* 7D   | (04)000001       */ stfld      bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
    IL_0017:  /* 2B   | 12               */ br.s       IL_002b

    .line 22,22 : 18,28 ''
//000022:             else if (x &lt; y) bigX = true; 
    IL_0019:  /* 06   |                  */ ldloc.0
    IL_001a:  /* 07   |                  */ ldloc.1
    IL_001b:  /* FE04 |                  */ clt
    IL_001d:  /* 16   |                  */ ldc.i4.0
    IL_001e:  /* FE01 |                  */ ceq
    IL_0020:  /* 0C   |                  */ stloc.2
    IL_0021:  /* 08   |                  */ ldloc.2
    IL_0022:  /* 2D   | 07               */ brtrue.s   IL_002b

    .line 22,22 : 29,41 ''
    IL_0024:  /* 02   |                  */ ldarg.0
    IL_0025:  /* 17   |                  */ ldc.i4.1
    IL_0026:  /* 7D   | (04)000001       */ stfld      bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
    .line 23,23 : 9,10 ''
//000023:         }
    IL_002b:  /* 2A   |                  */ ret
  } // end of method IfElseTernaryTest::RunIfElse

  .method /*06000004*/ public hidebysig instance void 
          RunTernary() cil managed
  // SIG: 20 00 01
  {
    // Method begins at RVA 0x2094
    // Code size       27 (0x1b)
    .maxstack  3
    .locals /*11000002*/ init ([0] int32 x,
             [1] int32 y)
    .line 25,25 : 9,10 ''
//000024:         public void RunTernary()
//000025:         {
    IL_0000:  /* 00   |                  */ nop
    .line 26,26 : 13,23 ''
//000026:             int x = 4; int y = 5;
    IL_0001:  /* 1A   |                  */ ldc.i4.4
    IL_0002:  /* 0A   |                  */ stloc.0
    .line 26,26 : 24,34 ''
    IL_0003:  /* 1B   |                  */ ldc.i4.5
    IL_0004:  /* 0B   |                  */ stloc.1
    .line 27,27 : 13,63 ''
//000027:             bigX = (x &gt; y) ? false : ((x &lt; y) ? true : false);
    IL_0005:  /* 02   |                  */ ldarg.0
    IL_0006:  /* 06   |                  */ ldloc.0
    IL_0007:  /* 07   |                  */ ldloc.1
    IL_0008:  /* 30   | 0A               */ bgt.s      IL_0014

    IL_000a:  /* 06   |                  */ ldloc.0
    IL_000b:  /* 07   |                  */ ldloc.1
    IL_000c:  /* 32   | 03               */ blt.s      IL_0011

    IL_000e:  /* 16   |                  */ ldc.i4.0
    IL_000f:  /* 2B   | 01               */ br.s       IL_0012

    IL_0011:  /* 17   |                  */ ldc.i4.1
    IL_0012:  /* 2B   | 01               */ br.s       IL_0015

    IL_0014:  /* 16   |                  */ ldc.i4.0
    IL_0015:  /* 7D   | (04)000001       */ stfld      bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
    .line 28,28 : 9,10 ''
//000028:         }
    IL_001a:  /* 2A   |                  */ ret
  } // end of method IfElseTernaryTest::RunTernary

Таким образом, кажется, что тернарный оператор короче и, я думаю, быстрее, чем меньше инструкций ... но на этом основании это, кажется, противоречит вашему случаю № 2, что удивительно ...

Edit: После комментария Ская, предлагающего «раздувание кода для № 2», это опровергнет то, что сказал Скай !!! Хорошо, код другой, контекст другой, это пример упражнения по проверке дампа IL, чтобы увидеть ...

4
ответ дан 28 November 2019 в 21:37
поделиться

Предполагая, что вас беспокоит производительность этого метода (а если нет, то зачем его публиковать?), вы должны рассмотреть возможность хранения значений char в массиве и преобразования значений Key в индекс массива.

1
ответ дан 28 November 2019 в 21:37
поделиться

У меня нет VS под рукой, но наверняка есть простой встроенный способ получить ключ в качестве персонажа? Что-то вроде метода toString , чтобы вы могли заменить этот чудовищный переключатель следующим:

if (shift)
  return inKey.toString().toUppercase();
else
  return inKey.toString().toLowercase();
0
ответ дан 28 November 2019 в 21:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: