Неохваченные случаи влияют на производительность переключения - PullRequest
1 голос
/ 07 февраля 2020

В C ++ у меня есть встроенная функция, которая содержит оператор switch-case. Я обнаружил, что когда запрограммирована какая-то ветвь speci c case , временные затраты на программу значительно возрастают, даже несмотря на то, что speci c case никогда не срабатывал во время выполнения.


Здесь показан пример кода:

#include <stdio.h>
#include <iostream>
#include <sys/time.h>
#include <string>

using namespace std;

enum Types {
    T0 = 0, T1, T2, T3, T4, T5, T6, T7, T8, T9, TS, TA, TB, TC
};

int64_t special(int64_t num, string str) {
    char buf[16];
    buf[0] = (num % 10) + '0';
    buf[1] = (num % 10) + '0';
    buf[2] = str.c_str()[0];
    return atoi(buf);
}

inline int64_t common(int64_t base, int64_t num) {
    return num + base;
}

inline int64_t myfunc(Types t, int64_t num) {
    string str;
    switch (t) {
    case T0:
        return 0;
        break;
#define CASE_TYPE(tv, base) \
    case tv: \
        return common(base, num); \
        break;
    CASE_TYPE(T1, 1)
    CASE_TYPE(T2, 2)
    CASE_TYPE(T3, 3)
    CASE_TYPE(T4, 4)
    CASE_TYPE(T5, 5)
    CASE_TYPE(T6, 6)
    CASE_TYPE(T7, 7)
    CASE_TYPE(T8, 8)
    CASE_TYPE(T9, 9)
#undef CASE_TYPE
    case TS:
        // Comment out the following 3 lines increases performance
        str = string((char*)&num, 4);
        return special(num, str);
        break;
        // Comment out the above 3 lines increases performance
    case TA:
    case TB:
    case TC:
        return 0;
        break;
    }
    return 0;
}

static const int LoopNum = 1000000000;

static inline int64_t now() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000;
}


// execution command line: ./test 1 1
int main(int argc, char *argv[])
{
    Types t = (Types)atoi(argv[1]); // t = T1
    int64_t num = (int64_t)atoi(argv[2]); // t = 1
    int64_t total = 0;
    int64_t start = now();
    for (int i = 0; i < LoopNum; i++) {
        total += myfunc(t, num);
    }
    cout << "Time Cost: " << now() - start << " ms" << endl;
    cout << "Result: " << total << endl;
    return 0;
}

В этой программе, когда строки в блоке case TS закомментированы, производительность значительно возрастает:

  • С блоком case TS: Стоимость времени = 2250 мс
  • Без блока case TS: Стоимость времени = 1492 мс

Программа компилируется и выполняется с помощью команды: g++ -o test -O2 test.cpp && ./test 1 1. С помощью этой команды значения переменных в программе: t = T1 и num = 1.

Проверено на Windows Subsystem for linux (Ubuntu 18.04) с g++ 7.4.0.


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

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

  • Программа в speci c case сложна. Например, когда программа содержит удаленный вызов процесса.
  • специфицирует c регистр не последний регистр, или значение регистра не является наибольшим значением в перечислении.

Я действительно понятия не имею, как это происходит и как этого избежать. Может ли кто-нибудь дать adivce? Будет полезен либо механизм, либо обходной путь. Благодаря.

Ответы [ 2 ]

4 голосов
/ 07 февраля 2020

Глядя на разборку в Compiler Explorer, разница в том, что когда вы закомментируете код для case TS, вы удаляете все использования локальной строковой переменной str. Компилятор полностью удаляет его, и результирующий код намного проще (так как нет никаких возможных вызовов конструктора или деструктора и, в частности, удаления, чтобы освободить память строки). Упрощенный код достаточно мал, чтобы поместиться в процессор L oop Stream Detector (LSD) процессора. ЛСД может значительно улучшить производительность тесных циклов, так как повторяющиеся инструкции остаются в очереди команд, избегая использования внешнего конвейера.

0 голосов
/ 07 февраля 2020

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

В качестве обходного пути вы можете захотеть выяснить, какие случаи чаще всего встречаются, и которые меньше, и разделяют «горячие» и «холодные» случаи на различные блоки switch / case и выбирают правильный с верхним уровнем if.

...