Эффективный оператор переключения - PullRequest
3 голосов
/ 04 декабря 2009

В следующих двух версиях коммутатора мне интересно, какая версия эффективна.

1

string* convertToString(int i)
{
    switch(i)
    {
    case 1:
        return new string("one");
    case 2:
        return new string("two");
    case 3:
        return new string("three");
        .
        .
    default:
        return new string("error");
    }
}

2

string* convertToString(int i)
{
    string *intAsString;
    switch(i)
    {
    case 1:
        intAsString = new string("one");
        break;
    case 2:
        intAsString = new string("two");
        break;
    case 3:
        intAsString = new string("three");
        break;
        .
        .
    default:
        intAsString = new string("error");
        break;
    }
return intAsString;
}

1: имеет несколько операторов возврата, это заставит компилятор генерировать дополнительный код?

Ответы [ 14 ]

29 голосов
/ 04 декабря 2009

Это преждевременное беспокойство по поводу оптимизации.

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

Вы должны (как обычно) профилировать свою программу, чтобы определить, находится ли эта функция даже в «горячем списке» для оптимизации. Это скажет вам, есть ли снижение производительности за использование break.

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

10 голосов
/ 04 декабря 2009

Оба есть.

Что вас действительно должно беспокоить, так это использование указателей здесь. Это необходимо? Кто удалит эти строки? Разве нет более простой альтернативы?

6 голосов
/ 04 декабря 2009

Не должно быть никакой разницы в скомпилированном коде. Тем не менее:

  1. Вы, вероятно, найдете возврат строк по значению более эффективным.
  2. Если строк много, подумайте о том, чтобы предварительно заполнить ими вектор (или объявить статический массив) и использовать i в качестве индекса в.
3 голосов
/ 04 декабря 2009

Оператор switch - это, в основном, серия операторов if в виде сгенерированных машинных инструкций. Одна из простых стратегий оптимизации заключается в размещении наиболее часто встречающегося case на первом месте в операторе switch.

Я также рекомендую то же решение, что и Себастьян, но без утверждения.

static const char *numberAsString[] = {
    "Zero",
    "One",
    "Two",
    "Three",
    "Four",
    "Five",
    "Six",
};

const char *ConvertToString(int num) {
  if (num < 1 || num >= (sizeof(numberAsString)/sizeof(char*))) 
    return "error";
  return numberAsString[num];
}
3 голосов
/ 04 декабря 2009

Вы никогда не сможете узнать, как оптимизация повлияет на создаваемый код, если вы не скомпилируете с определенной версией компилятора, конкретным набором настроек и конкретной базой кода.

Оптимизирующие компиляторы C ++ могут решить перевернуть ваш исходный код, чтобы получить специальную оптимизацию, доступную только для архитектуры компилятора, и так, даже если вы об этом не знаете. Мощный оптимизирующий компилятор может, например, выясните, что когда-либо нужны только 2 из 10 случаев, и это оптимизирует весь оператор switch-case.

Итак, мой ответ на ваш вопрос: Му .

2 голосов
/ 04 декабря 2009

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

1 голос
/ 04 декабря 2009

Вы оптимизируете [*] операторы переключателя, выполняя как можно меньше работы с переключателем (поскольку неясно, будет ли компилятор распространять дублирование). Если вы настаиваете на возврате строки по указателю и использовании оператора switch, я бы написал:

string *convertToString(int i) {
    const char *str;
    switch(i) {
        case 1 : str = "one"; break;
        // etc
        default : str = "error"; break;
    }
    return new string(str);
}

Но, конечно, для этого примера я бы, вероятно, просто использовал справочную таблицу:

const char *values[] = {"error", "one", ... };

string convertToString(unsigned int i) {
    if (i >= sizeof(values)/sizeof(*values)) i = 0;
    return values[i];
}

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

[*] Здесь я имею в виду тот тип оптимизации, который используется при написании переносимого кода или в первой версии в надежде на создание понятного для чтения кода, который не потребует слишком много реальная оптимизация. Реальная оптимизация включает в себя реальные измерения.

1 голос
/ 04 декабря 2009

Рассмотрим сохранение строк в качестве статических констант:

static char const g_aaczNUMBER[][] = 
    {
        {"Zero"}, { "One" }, ...
    };

static char const g_aczERROR[] = { "Error" };

char* convertIntToString(int i) const { 
    return i<0 || 9<i ? g_aczERROR : g_aaczNUMBER[i]; 
}
1 голос
/ 04 декабря 2009

Я бы предложил что-то в форме:

void CScope::ToStr( int i, std::string& strOutput )
{
   switch( i )
   {
   case 1:
        strOutput = "Some text involving the number 1";

   ... etc etc
}

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

1 голос
/ 04 декабря 2009

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

...