If-else-if по сравнению с картой

Предположим, что у меня есть такая if/else-if цепочка:

if( x.GetId() == 1 )
{
}
else if( x.GetId() == 2 )
{
}
// ... 50 more else if statements

То, что интересно, если я сохраню карту, то это будет немного лучше с точки зрения производительности? (принятие ключей является целыми числами),

5
задан Georg Fritzsche 17 March 2010 в 07:02
поделиться

7 ответов

почему вы не используете переключатель?

swich(x.GetId())
{
  case 1: /* do work */ break; // From the most used case
  case 2: /* do work */ break; 
  case ...: // To the less used case
}

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

Поместите наиболее часто используемый регистр в верхнюю часть переключателя (здесь могут быть некоторые проблема с производительностью, если x.GetId обычно равно 50)

5
ответ дан 18 December 2019 в 09:48
поделиться

переключатель - лучшее, что я думаю

{ {1}}
2
ответ дан 18 December 2019 в 09:48
поделиться

Ответ, как и на большинство вопросов, связанных с производительностью, может быть.

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

[далее, переключатель относится к общей цепочке if / else ]

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

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

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

0
ответ дан 18 December 2019 в 09:48
поделиться

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

Но если вы просто устанавливаете / возвращаете значение или имя на основе идентификатора, тогда ДА. Карта - это именно то, что вам нужно. Контейнеры STL оптимизированы, и если вы думаете, что можете оптимизировать лучше, то вы либо невероятно умны, либо поразительно глупы.

например, один вызов с использованием std :: map с именем mymap,

thisvar = mymap[x.getID()];

намного лучше, чем 50 из этих

if(x.getID() == ...){thisvar = ...;}

, потому что он более эффективен при увеличении количества идентификаторов. Если вас интересует, почему, поищите хорошее руководство по структурам данных.

Но на что я действительно обращаю внимание, так это на время обслуживания / ремонта. Если вам нужно изменить имя переменной или отказаться от использования getID () или getName (), или внести какие-либо незначительные изменения, вы должны сделать это ПЯТЬДЕСЯТ РАЗ в своем примере. И вам нужна новая строка каждый раз, когда вы добавляете идентификатор.

Карта сокращает это до одной смены кода. НЕ ВАЖНО, СКОЛЬКО У ВАС ИМЕЕТСЯ ID.

Тем не менее, если вы действительно выполняете разные действия для каждого идентификатора, лучше использовать switch-case. Используя switch-case, а не операторы if, вы можете улучшить производительность и удобочитаемость. См. Здесь: Преимущество переключения оператора if-else Я бы избегал указателей на функции, если вы не очень понимаете, как они улучшат ваш код, потому что, если вы не 100% уверенность в том, что вы делаете, синтаксис может быть испорчен, и это избыточно для всего, для чего вы могли бы использовать карту.

В принципе, меня интересует проблема, которую вы пытаетесь решить.Возможно, вам будет лучше с картой или чемоданом, но если вы думаете, что можете использовать карту, это АБСОЛЮТНО то, что вы должны использовать вместо этого.

0
ответ дан 18 December 2019 в 09:48
поделиться

Карты (обычно) реализуются с использованием красно-черных деревьев, что дает O (log N) просмотров, поскольку дерево постоянно поддерживается в равновесии. Ваш линейный список операторов if будет наихудшим случаем O (N). Итак, да, карта будет значительно быстрее для поиска.

Многие люди рекомендуют использовать оператор switch, который может быть не быстрее для вас, в зависимости от ваших фактических операторов if. Компилятор может иногда оптимизировать переключение, используя таблицу переходов, которая будет O (1), но это возможно только для значений, которые не определены критерием; следовательно, такое поведение может быть в некоторой степени недетерминированным. Хотя здесь есть отличная статья с несколькими советами по оптимизации операторов switch Оптимизация кода C и C ++ .

Технически вы даже можете составить сбалансированное дерево вручную, это лучше всего работает для статических данных, и я недавно создал функцию для быстрого поиска того, какой бит был установлен в байте (это использовалось во встроенном приложении на I / O pin прерывание и должно было быть быстрым, когда в 99% случаев в байте был установлен только 1 бит):

unsigned char single_bit_index(unsigned char bit) {
    // Hard-coded balanced tree lookup
    if(bit > 0x08)
        if(bit > 0x20)
            if(bit == 0x40)
                return 6;
            else
                return 7;
        else
            if(bit == 0x10)
                return 4;
            else
                return 5;
    else
        if(bit > 0x02)
            if(bit == 0x04)
                return 2;
            else
                return 3;
        else
            if(bit == 0x01)
                return 0;
            else
                return 1;
}

Это дает постоянный поиск в 3 шага для любого из 8 значений, что дает мне очень детерминированную производительность, линейный поиск - с заданными случайными данными - будет в среднем 4 шага поиска, с лучшим случаем 1 и худшим случаем 8 шагами.

Это хороший пример диапазона, который компилятор, вероятно, не будет оптимизировать для таблицы переходов, поскольку 8 значений, которые я ищу, так далеко друг от друга: 1, 2, 4, 8, 16, 32, 64 и 128. Пришлось бы создать очень разреженную таблицу на 128 позиций, содержащую всего 8 элементов, содержащую цель, что на ПК с тонной оперативной памяти могло бы не иметь большого значения, но на микроконтроллере это было бы убийственно.

10
ответ дан 18 December 2019 в 09:48
поделиться

Лучшим решением будет оператор switch . Это позволит вам проверить значение x.GetId () только один раз, а не (в среднем) 25 раз, как сейчас делает ваш код.

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

0
ответ дан 18 December 2019 в 09:48
поделиться

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

#include <cstdio>
#include <cstdlib>
#include <ctime>

const unsigned int Max = 4;

void f1();
void f2();
void f3();
void f4();
void (*vp[Max])();

int main()
{
    vp[ 0 ] = f1;
    vp[ 1 ] = f2;
    vp[ 2 ] = f3;
    vp[ 3 ] = f4;

    srand( std::time( NULL ) );
    vp[( rand() % Max )]();
}

void f1()
{
    std::printf( "Hello from f1!\n" );
}

void f2()
{
    std::printf( "Hello from f2!\n" );
}

void f3()
{
    std::printf( "Hello from f3!\n" );
}

void f4()
{
    std::printf( "Hello from f4!\n" );
}
0
ответ дан 18 December 2019 в 09:48
поделиться
Другие вопросы по тегам:

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