簡體   English   中英

unordered_map 與帶有 int 鍵的向量

[英]unordered_map vs vector with int keys

當鍵是整數時,使用std::unordered_mapstd::vector在性能上有何不同。 我有大約 100-1000 個具有連續 ID 的元素,我可以使用它們來訪問向量。 使用 hash 表的原因是通過對象本身進行索引更方便。

想象三種不同的情況:

  • 寫密集型
  • 精讀
  • 均衡

請注意,我將其作為一般性問題提出,而不是作為特定於代碼的問題。

使用最方便的容器來完成任務

通常來說,一般來說。

如果你有100-1000元素,容器本身並不重要——使用std::map甚至比std::unordered_map更好,例如,如果你需要調試打印內容——除非你以某種方式依賴在哈希上。 當您獲得 10 萬多個元素時,容器性能開始變得有趣。

一般來說:

由於缺乏間接性,Vector 具有更好的緩存一致性。 訪問索引更快,因為不需要計算散列函數。 迭代具有可預測的分支。

無序映射使用稀疏結構使用更少的內存(你說索引是連續的,所以這個優勢不適用於你)。 使用無序映射在任意索引中添加或刪除元素的速度越來越快。

當您只有很少的元素(例如 100-1000)時,漸近復雜性不一定重要。 在這種情況下,緩存一致性和分支預測往往占主導地位。

首先選擇哪種數據結構更方便。 然后衡量訪問該結構是否對整個程序的性能產生重大影響。 如果是,則測量與其他數據結構的差異,看看它是否明顯更快(與測量方差有關)。

vectormap在處理鍵的方式上存在重大差異。 忽略性能,並在刪除節點時使用對密鑰具有正確行為的任何一個。

如果您的項目少於 ~1000 個,那么性能並不重要。

我只是想知道同樣的問題。 雖然我通常同意在大多數情況下應該使用最方便的容器,並且當有疑問時,您應該自己測量,但我認為至少對我們正在談論的尺寸有一個大概的了解是很好的。

因此,我實現了一個小例子:

#include <string>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <chrono>

struct MyStruct {
    int value;
};


int main(int argc, char** argv) {
    size_t const SIZE = 100;
    size_t const ACCESSES = 10000;

    std::vector<MyStruct const*> my_structs;
    for (size_t i = 0; i < SIZE; ++i) {
        my_structs.push_back(new MyStruct());
    }
    
    std::vector<double> values_vec(my_structs.size());
    std::unordered_map<MyStruct const*, double> values_map;
    for (size_t i = 0; i < SIZE; ++i) {
        double rand_val = (rand() % 1000)/10;

        values_vec[i] = rand_val;
        values_map[my_structs[i]] = rand_val;
    }

    std::vector<MyStruct const*> queries_ptr;
    std::vector<size_t> queries_int;
    for (size_t i = 0; i < ACCESSES; ++i) {
        size_t idx = rand() % SIZE;
        queries_int.push_back(idx);
        queries_ptr.push_back(my_structs[idx]);
    }

    auto begin_vec = std::chrono::steady_clock::now();
    double large_sum_vec = 0;
    for (size_t query : queries_int) {
        large_sum_vec += values_vec[query];
    }
    auto end_vec = std::chrono::steady_clock::now();

    double large_sum_map = 0;
    for (MyStruct const* query : queries_ptr) {
        large_sum_map += values_map[query];
    }
    auto end_map = std::chrono::steady_clock::now();

    std::cout << "Results for " << ACCESSES << " accesses to vector of size " << SIZE << std::endl;
    std::cout << "=== VEC === Result = " << large_sum_vec << " Time = " << std::chrono::duration_cast<std::chrono::microseconds>(end_vec - begin_vec).count() << " microseconds" << std::endl;
    std::cout << "=== MAP === Result = " << large_sum_vec << " Time = " << std::chrono::duration_cast<std::chrono::microseconds>(end_map - end_vec).count() << " microseconds" << std::endl;

    for (size_t i = 0; i < SIZE; ++i) {
        delete my_structs[i];
    }
}

您可以在 Coliru 上找到它: https://coliru.stacked-crooked.com/a/a986dd2607a8566a

結果:帶索引的 std::vector 比隨機讀取訪問的 std::unordered_map 快大約 10-20 倍

有趣的是,兩種運行時間幾乎都與數據大小無關。 隨着訪問次數的增加,該關系更趨向於 1:20。 我沒有測試寫密集型/混合型,但我不認為會有太大差異,因為此時已經找到了元素。

暫無
暫無

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

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