【问题标题】:Unreached cases affect switch-case performance未达到的案例会影响开关案例的性能
【发布时间】:2020-05-23 16:13:33
【问题描述】:

在 C++ 中,我有一个包含 switch-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 &amp;&amp; ./test 1 1。使用此命令,程序中的变量值为t = T1num = 1

Windows Subsystem for linux (Ubuntu 18.04)g++ 7.4.0 上测试。


令人困惑的是,这个问题并不总是出现。我对如何编写这样的代码没有清楚的了解(但上面的例子确实出现了这个问题)。

根据我的测试,似乎在以下一些情况下会出现问题:

  • 具体案例中的程序很复杂。例如。当程序包含远程进程调用时。
  • 特定案例不是最后一个案例,或者案例值不是枚举中的最大值。

我真的不知道这是如何发生的以及如何避免它。任何人都可以提供任何建议吗?无论是机制还是变通方法都会有所帮助。谢谢。

【问题讨论】:

  • 您的代码中至少有两个问题会导致未定义的行为。首先,您不能将整数转换为字符串;其次,您在没有显式字符串空终止符的数组上使用字符串函数。
  • @Some 程序员老兄 谢谢。但请忽略这些问题。我只想添加一些完整的逻辑来重现我的问题。正如我所提到的,由于从未执行过有关字符串的操作,因此潜在的风险不是问题。当然,这些问题在实际程序中是必须要注意的。

标签: c++ switch-statement


【解决方案1】:

查看 Compiler Explorer 上的反汇编,不同之处在于,当您注释掉 case TS 的代码时,您删除了对本地 str 字符串变量的所有使用。编译器完全删除它,生成的代码更简单(因为不可能调用构造函数或析构函数,特别是删除以释放字符串的内存)。简化的代码足够小,可以放入处理器的循环流检测器 (LSD)。 LSD 可以大大提高紧密循环的性能,因为重复指令驻留在指令队列中,避免使用前端管道。

【讨论】:

  • 谢谢!这真的很有帮助!您是否知道如何在保持包含字符串操作的特定案例的同时提高性能?我的一个想法是调用另一个实现这些操作的非内联函数。这样我在内联函数中就没有任何字符串操作(包括构造/破坏)。此解决方案在某些情况下似乎很有用。但是在其他一些情况下(调用逻辑比较复杂),问题依然存在……如何确保我的程序运行速度与没有特定情况的程序一样快?
【解决方案2】:

您必须检查程序的反汇编程序。我的猜测是编译器选择了不同的策略来实现 switch 语句,这对您的情况来说效率较低。

作为一种解决方法,您可能需要调查哪些情况更有可能受到打击,哪些情况不太可能,并将“热”情况和“冷”情况分成不同的switch/case 块并选择右边是上层if

【讨论】:

    猜你喜欢
    • 2021-09-29
    • 1970-01-01
    • 1970-01-01
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 1970-01-01
    相关资源
    最近更新 更多