Как & ldquo; switch & ldquo; компилировать? [Дубликат]

Другим случаем, когда NullReferenceExceptions может случиться, является (неправильное) использование оператора as :

class Book {
    public string Name { get; set; }
}
class Car { }

Car mycar = new Car();
Book mybook = mycar as Book;   // Incompatible conversion --> mybook = null

Console.WriteLine(mybook.Name);   // NullReferenceException

Здесь Book и Car являются несовместимыми типами; a Car не может быть преобразован / передан в Book. Когда этот сбой завершается неудачно, as возвращает null. Используя mybook после этого, вы вызываете NullReferenceException.

В общем случае вы должны использовать cast или as, как показано ниже:

Если вы ожидаете преобразования типа в всегда преуспевает (т. е. вы знаете, какой объект должен быть впереди времени), тогда вы должны использовать cast:

ComicBook cb = (ComicBook)specificBook;

Если вы не уверены в типе, но хотите попробовать , чтобы использовать его как определенный тип, затем используйте as:

ComicBook cb = specificBook as ComicBook;
if (cb != null) {
   // ...
}

103
задан Dirk Vollmar 15 January 2009 в 00:13
поделиться

8 ответов

Компилятор может создавать таблицы перехода, где это применимо. Например, когда вы используете рефлектор для просмотра кода, вы увидите, что для огромных переключателей на строках компилятор будет генерировать код, который использует хэш-таблицу для отправки этих данных. Хэш-таблица использует строки как ключи и делегирует кодам case как значения.

У этого есть асимптотическое лучшее время выполнения, чем много цепей if, и на самом деле быстрее даже для относительно небольшого количества строк.

170
ответ дан Konrad Rudolph 31 August 2018 в 09:50
поделиться
  • 1
    Хороший ответ, интересный о хэш-таблице. – BobbyShaftoe 15 January 2009 в 00:19
  • 2
    В некоторых случаях они также преобразуются в сравнение деревьев. Обоснование несколько сложное, но в основном сводится к таблице, направленной на нейтрализацию современных целевых буферов для прыжков cpu, и таким образом уничтожает предиктор ветвления. Я смутно вспоминаю документ на конференции GCC по codegen для коммутаторов. – olliej 15 January 2009 в 00:46
  • 3
    Это означает: switch (a) case "x": case "y": case "z": // что-то сломается; } быстрее, чем: if (a == "x" || a == "b" || a == "c") // что-то в порядке? – yazanpro 16 November 2012 в 23:19
  • 4
    здесь у нас нет вложенных, если еще, только OR, так что вы думаете? – yazanpro 16 November 2012 в 23:20
  • 5
    @yazanpro По старым компиляторам потенциально да (но обратите внимание, что количество случаев настолько мало, что это может не иметь значения!). Однако современные компиляторы делают намного больше анализа кода. Как следствие, они могут понять, что эти два фрагмента кода эквивалентны и применяют те же самые оптимизации. Но это чисто спекуляция с моей стороны, я не знаю, действительно ли это делает компилятор. – Konrad Rudolph 17 November 2012 в 12:12

Статистика отсутствия соответствия может быть не очень хорошей.

Если вы действительно загружаете источник, то значения совпадения, как известно, равны 21, как в случае if, так и в switch. Компилятор должен уметь абстрагироваться, зная, какой оператор должен выполняться в любое время, и процессор должен иметь возможность правильно предсказать ветку.

Более интересный случай - когда не каждый случай ломается, в моем но это, возможно, не было предметом эксперимента.

4
ответ дан Calyth 31 August 2018 в 09:50
поделиться

Конрад правильный. В случае включения непрерывных диапазонов целых чисел (например, где у вас есть случай 0, случай 1, случай 2 .. случай n), компилятор может сделать что-то еще лучше, потому что ему даже не нужно создавать хеш-таблицу; он просто хранит массив указателей на функции и, таким образом, может загружать свою цель перехода в постоянное время.

28
ответ дан Community 31 August 2018 в 09:50
поделиться
  • 1
    Итак, вы добавили ответ, чтобы сказать, что другой ответ правильный? – md1337 1 July 2013 в 21:25
  • 2
    да, хороший способ одобрить другие ответы: D – Usman Younas 22 May 2015 в 06:16

Это код для микроконтроллера PIC18 на языке C:

void main() {
int s1='0';
int d0;
int d1;
//if (s1 == '0') {d1 = '0'; d0 = '0';}
//else if (s1 == '1') {d1 = '0';d0 = '1';}
//else if (s1 == '2') {d1 = '1';d0 = '0';}
//else if (s1 == '3') {d1 = '1';d0 = '1';}
switch (s1) {
      case  '0': {d1 = '0';d0 = '0';} break;
      case  '1': {d1 = '0';d0 = '1';} break;
      case  '2': {d1 = '1';d0 = '0';} break;
      case  '3': {d1 = '1';d0 = '1';} break;
    }
}

С ifs

s1='0' - 14 cycles
s1='1' - 21 cycles
s1='2' - 28 cycles
s1='3' - 33 cycles
s1='4' - 34 cycles

С помощью случаев

s1='0' - 17 cycles
s2='1' - 23 cycles
s3='2' - 29 cycles
s4='3' - 35 cycles
s5='4' - 32 cycles

So i может предположить, что на очень низком уровне ifs быстрее. Код в ПЗУ также короче.

0
ответ дан Denys Titarenko 31 August 2018 в 09:50
поделиться
  • 1
    Компилятор PIC18 C ничего не говорит о компиляторе .net C#. – Morgoth 7 September 2017 в 11:55

Как сказал Конрад, компилятор может построить таблицу Jump.

В C ++ причина, по которой это возможно, связана с ограничением коммутаторов.

5
ответ дан J.J. 31 August 2018 в 09:50
поделиться

Это небольшое упрощение, как обычно, любой современный компилятор, который встречает последовательность if..else if .., которая может быть тривиально преобразована в оператор switch человеком, компилятор также будет. Но просто для того, чтобы добавить дополнительную забаву, компилятор не ограничен синтаксисом, поэтому он может генерировать «переключающие», подобные заявления внутри, которые имеют сочетание диапазонов, одиночных целей и т. Д., И они могут (и делать) делать это как для коммутатора, так и для. .else.

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

switch(a) { case 0: ...; break; case 1: ...; break; }

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

13
ответ дан olliej 31 August 2018 в 09:50
поделиться

Единственное преимущество случая if over - это когда наблюдается заметное увеличение частоты появления первого случая.

Не уверен, где именно находится порог, но я использую синтаксис case, если первый «почти всегда» не проходит первый тест.

0
ответ дан Ralph 31 August 2018 в 09:50
поделиться

Операторы switch / case могут быть обычно более быстрыми на 1 уровне, но когда вы начинаете получать 2 или более, инструкции switch / case начинаются в 2-3 раза до тех пор, пока вложенные операторы if / else.

В этой статье приведены некоторые сравнения скорости , выделяющие различия в скорости, когда такие операторы вложены.

Например, в соответствии с их тестами пример кода выглядит следующим образом:

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

закончил в half время, в течение которого выполнялся эквивалентный оператор switch / case:

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

Да, это рудиментарный пример, но он иллюстрирует суть.

Таким образом, вывод может заключаться в использовании switch / case для простых типов, которые только на одном уровне, но для более сложных сравнений и множественных вложенных уровней используются классические конструкции if / else?

5
ответ дан Thrawn Wannabe 31 August 2018 в 09:50
поделиться
Другие вопросы по тегам:

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