循环内切换的性能

时间:2018-05-06 18:44:55

标签: c++ performance g++

我们有一个消息类型映射到消息列表。给出一个性能关键的代码,例如:

struct row_t {
    int message_type; //0,1,2,3,4,5
};
map<int, vector<row_t>> message_map;

for (auto x : message_map) {
    int message_type = x.first;
    vector<row_t> message_rows = x.second;

    for (row_t row : message_rows) {
        //LARGE CODE CHUNK
        switch(row.message_type) { //same as switch(message_type)
            case 0:
                add_0_to_database();
                break;
            case 1:
                add_0_to_database();
                break;
            //...
            default:
                break;
        }
    }
}

switch语句将在内部循环的每次迭代中执行,即使message_rows中的每个元素都具有相同的类型。

这个问题可以消除,只在内循环开始之前运行一次switch语句:

for (auto x : message_map) {
    int message_type = x.first;
    vector<row_t> message_rows = x.second;

    switch(message_type) {
        case 0:
            for (row_t row : message_rows) {
                //LARGE CODE CHUNK
                add_0_to_database(row);
            }
            break;
        case 1:
            for (row_t row : message_rows) {
                //LARGE CODE CHUNK
                add_1_to_database(row);
            }
            break;
        //...
        default:
            break;
    }
}

但是现在我们有多个冗余的内部循环和#34; LARGE CODE CHUNK&#34;代码需要多次重复。

我的问题:现代编译器(特别是g ++)可以优化版本1以与版本2一样高效吗?

或者我应该使用版本2,也许可以考虑使用其他方法来删除冗余,例如在switch语句中将函数指针设置为add_{0/1}_to_database,然后在循环中使用函数指针?

1 个答案:

答案 0 :(得分:0)

据推测,您的真实row_t比问题中显示的要复杂得多。如果不是,则无需循环向量。只需使用长度。

与性能问题一样,首先要做的是测试。如果您的代码库的这部分不是性能关键的话,则无需担心。使您的代码干净简单。这个答案的其余部分假设测试表明这确实是一个性能瓶颈。在我找到解决方案之前,我将首先解决两个可能是更大的性能问题的项目。

  1. 使用std::map。遍历有序地图可能很昂贵。考虑切换到{+ 1}},这是在c ++ 11中引入的。使用基于范围的循环表示您使用的是c ++ 11或更高版本。

  2. 过度复制。外部循环std::unordered_map制作副本,for (auto x : message_map)vector<row_t> message_rows = x.second也是如此。您正在复制矢量两次,以及每个元素的附加副本。使用参考文献。

  3. 解决方案:考虑使用函数指针。一个简单的方法是将switch语句移出内部循环,但不是像问题中所示的循环,而是设置一个函数指针:

    for (row_t row : message_rows)

    那个switch语句有点难看。 2.0版将其转换为另一个地图:

    for (auto& x : message_map) // Note the use of auto& to avoid copying.
    {
        int message_type = x.first;
        std::vector<row_t>& message_rows = x.second; // Avoid copying!
        void (* add_to_database)(const row_t&) = nullptr;
    
        switch(message_type)
        {
        case 0:
            add_to_database = add_0_to_database;
            break;
        case 1:
            add_to_database = add_1_to_database;
            break;
        //...
        default:
            // This might be an error that should be handled here.
            break;
        }
    
        for (row_t& row : message_rows) // Avoid copying!
        {
            // LARGE CODE CHUNK
            add_to_database (row);
        }
    }