簡體   English   中英

循環內開關的性能

[英]Performance of switch within a loop

我們有消息類型到消息列表的映射。 給定對性能至關重要的代碼,例如:

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;
        }
    }
}

即使message_rows中的每個元素都具有相同的類型,switch語句也會在內部循環的每次迭代中執行。

可以消除此問題,僅在內部循環開始之前運行一次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;
    }
}

但是,現在我們有多個冗余內部循環,並且“大代碼塊”代碼需要重復幾次。

我的問題:現代編譯器(尤其是g ++)是否可以將版本1優化為與版本2一樣有效?

還是應該使用版本2,也許考慮使用其他方法來消除冗余,例如在switch語句add_{0/1}_to_database函數指針設置為add_{0/1}_to_database ,然后在循環中使用函數指針?

大概您實際的row_t比問題中顯示的要復雜。 如果不是,則無需遍歷向量。 只需使用長度即可。

與性能問題一樣,第一件事就是測試。 如果您的代碼庫的這一部分對性能不是至關重要的,則無需擔心。 使您的代碼簡潔明了。 該答案的其余部分假定測試表明這確實是性能瓶頸。 在獲得解決方案之前,我將首先解決兩個問題,這些問題可能是更大的性能問題。

  1. 使用std::map 遍歷有序地圖可能會很昂貴。 考慮切換到c ++ 11中引入的std::unordered_map 您使用基於范圍的循環表示您正在使用c ++ 11或更高版本。

  2. 復制過多。 for (auto x : message_map)的外循環for (auto x : message_map) vector<row_t> message_rows = x.secondfor (row_t row : message_rows)的外循環都for (auto x : message_map)復制。 您將向量復制兩次,外加每個元素的另一個副本。 使用參考。

解決方案:考慮使用函數指針。 一種簡單的方法是將switch語句移出內部循環,但不要像問題中所示那樣進行循環,而是設置一個函數指針:

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);
    }
}

該switch語句有點難看。 2.0版將其轉換為另一張地圖:

typedef void (*AddFunction)(const row_t&);
std::unordered_map<int, AddFunction> add_function_map;

// Outside the loop, populate the map:
add_function_map[0] = add_0_to_database;
add_function_map[1] = add_1_to_database;
// Rest of add_function_map population elided.

// The loop is now short and sweet.
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!
    AddFunction add_to_database = add_function_map[message_type];
    for (row_t& row : message_rows) // Avoid copying!
    {
        // LARGE CODE CHUNK
        add_to_database (row);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM