簡體   English   中英

gcc std :: unordered_map的執行速度慢嗎?如果是這樣 - 為什么?

[英]Is gcc std::unordered_map implementation slow? If so - why?

我們正在用C ++開發一個高性能的關鍵軟件。 我們需要一個並發的哈希映射並實現一個。 所以我們寫了一個基准來弄清楚,我們的並發哈希映射與std::unordered_map相比要慢多少。

但是, std::unordered_map似乎非常慢......所以這是我們的微基准測試(對於並發映射,我們產生了一個新的線程,以確保鎖定不會被優化掉,並注意我從來沒有因為我因為我還可以使用google::dense_hash_map基准測試,這需要一個空值):

boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
    uint64_t val = 0;
    while (val == 0) {
        val = dist(rng);
    }
    vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
    map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
    val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;

(編輯:整個源代碼可以在這里找到: http//pastebin.com/vPqf7eya

std::unordered_map的結果是:

inserts: 35126
get    : 2959

對於google::dense_map

inserts: 3653
get    : 816

對於我們手工支持的並發映射(它執行鎖定,雖然基准測試是單線程的 - 但是在單獨的生成線程中):

inserts: 5213
get    : 2594

如果我在沒有pthread支持的情況下編譯基准程序並在主線程中運行所有內容,我會得到以下手工支持並發映射的結果:

inserts: 4441
get    : 1180

我用以下命令編譯:

g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc

所以特別是在std::unordered_map上的插入似乎非常昂貴 - 其他地圖的時間為35秒vs 3-5秒。 查找時間似乎也很高。

我的問題:這是為什么? 我在stackoverflow上讀了另一個問題,有人問,為什么std::tr1::unordered_map比他自己的實現慢。 有最高級別的答案狀態, std::tr1::unordered_map需要實現更復雜的接口。 但是我看不出這個論點:我們在concurrent_map中使用了bucket方法, std::unordered_map使用了bucket-approach( google::dense_hash_map沒有,但是std::unordered_map應該至少和我們的手一樣快)支持並發安全版本?)。 除此之外,我在界面中看不到強制使哈希映射表現不佳的功能的任何內容......

所以我的問題是: std::unordered_map似乎很慢嗎? 如果不是:出了什么問題? 如果是:這是什么原因。

而我的主要問題是:為什么在std::unordered_map插入一個值如此可怕的昂貴(即使我們在開始時保留了足夠的空間,它也沒有更好的表現 - 所以重新散列似乎不是問題)?

編輯:

首先:是的,所提出的基准測試並不完美 - 這是因為我們玩了很多它並且它只是一個黑客(例如生成int的uint64發行版在實踐中不是一個好主意,在一個中排除0循環是一種愚蠢的等...)。

目前大多數評論都解釋說,我可以通過為它預分配足夠的空間來加快unordered_map的速度。 在我們的應用程序中,這是不可能的:我們正在開發一個數據庫管理系統,並且需要一個哈希映射來在事務期間存儲一些數據(例如鎖定信息)。 因此,這個映射可以是從1(用戶只進行一次插入和提交)到數十億條目(如果發生全表掃描)的所有內容。 在這里預先分配足夠的空間是不可能的(並且在開始時分配很多將消耗太多內存)。

此外,我很抱歉,我沒有說清楚我的問題:我對制作unordered_map並不感興趣(使用googles密集哈希映射對我們來說很好),我只是不明白這個巨大的性能差異來自何處。 它不能只是預分配(即使有足夠的預分配內存,密集映射比unordered_map快一個數量級,我們的手動並發映射以大小為64的數組開始 - 所以比unordered_map小一個)。

那么std::unordered_map這種糟糕表現的原因是什么? 或者有不同的問題:是否可以編寫std::unordered_map接口的實現,該接口是標准符合和(幾乎)與googles密集哈希映射一樣快? 或者標准中是否有某些內容強制實施者選擇低效的方式來實現它?

編輯2:

通過分析,我看到很多時間用於整數divions。 std::unordered_map使用素數作為數組大小,而其他實現使用2的冪。 為什么std::unordered_map使用素數? 如果散列是壞的,要更好地執行? 對於好的哈希,它確實沒有任何區別。

編輯3:

這些是std::map的數字:

inserts: 16462
get    : 16978

Sooooooo:為什么插入std::map比插入std::unordered_map更快...我的意思是WAT? std::map有一個更糟糕的局部性(樹與數組),需要進行更多的分配(每次插入vs每次重新加上+每次碰撞加1次),最重要的是:有另一種算法復雜度(O(logn)vs O( 1))!

我找到了原因:這是gcc-4.7的問題!!

gcc-4.7

inserts: 37728
get    : 2985

gcc-4.6

inserts: 2531
get    : 1565

所以gcc-4.7中的std::unordered_map被破壞了(或者我的安裝,這是在Ubuntu上安裝gcc-4.7.0 - 另一個是在debian測試中安裝gcc 4.7.1)。

我將提交錯誤報告..直到那時:不要使用std::unordered_map和gcc 4.7!

我猜你沒有正確調整你的unordered_map大小,正如Ylisar建議的那樣。 當鏈在unordered_map變得太長時,g ++實現將自動重新散列到更大的哈希表,這將對性能產生很大的影響。 如果我沒記錯的話, unordered_map默認為(最小的素數大於) 100

我的系統上沒有chrono ,所以我用times()計時。

template <typename TEST>
void time_test (TEST t, const char *m) {
    struct tms start;
    struct tms finish;
    long ticks_per_second;

    times(&start);
    t();
    times(&finish);
    ticks_per_second = sysconf(_SC_CLK_TCK);
    std::cout << "elapsed: "
              << ((finish.tms_utime - start.tms_utime
                   + finish.tms_stime - start.tms_stime)
                  / (1.0 * ticks_per_second))
              << " " << m << std::endl;
}

我使用了10000000SIZE ,並且不得不為我的boost版本改變一些東西。 另請注意,我預先調整哈希表的大小以匹配SIZE/DEPTH ,其中DEPTH是由於哈希沖突導致的桶鏈長度的估計。

編輯: Howard在評論中指出unordered_map的最大加載因子為1 因此, DEPTH控制代碼重新發送的次數。

#define SIZE 10000000
#define DEPTH 3
std::vector<uint64_t> vec(SIZE);
boost::mt19937 rng;
boost::uniform_int<uint64_t> dist(std::numeric_limits<uint64_t>::min(),
                                  std::numeric_limits<uint64_t>::max());
std::unordered_map<int, long double> map(SIZE/DEPTH);

void
test_insert () {
    for (int i = 0; i < SIZE; ++i) {
        map[vec[i]] = 0.0;
    }
}

void
test_get () {
    long double val;
    for (int i = 0; i < SIZE; ++i) {
        val = map[vec[i]];
    }
}

int main () {
    for (int i = 0; i < SIZE; ++i) {
        uint64_t val = 0;
        while (val == 0) {
            val = dist(rng);
        }
        vec[i] = val;
    }
    time_test(test_insert, "inserts");
    std::random_shuffle(vec.begin(), vec.end());
    time_test(test_insert, "get");
}

編輯:

我修改了代碼,以便我可以更輕松地更改DEPTH

#ifndef DEPTH
#define DEPTH 10000000
#endif

因此,默認情況下,選擇哈希表的最差大小。

elapsed: 7.12 inserts, elapsed: 2.32 get, -DDEPTH=10000000
elapsed: 6.99 inserts, elapsed: 2.58 get, -DDEPTH=1000000
elapsed: 8.94 inserts, elapsed: 2.18 get, -DDEPTH=100000
elapsed: 5.23 inserts, elapsed: 2.41 get, -DDEPTH=10000
elapsed: 5.35 inserts, elapsed: 2.55 get, -DDEPTH=1000
elapsed: 6.29 inserts, elapsed: 2.05 get, -DDEPTH=100
elapsed: 6.76 inserts, elapsed: 2.03 get, -DDEPTH=10
elapsed: 2.86 inserts, elapsed: 2.29 get, -DDEPTH=1

我的結論是,除了使其等於整個預期的唯一插入數之外,任何初始哈希表大小都沒有太大的性能差異。 此外,我沒有看到您正在觀察的數量級性能差異。

我使用64位/ AMD / 4內核(2.1GHz)計算機運行您的代碼,它給了我以下結果:

MinGW-W64 4.9.2:

使用std :: unordered_map:

inserts: 9280 
get: 3302

使用std :: map:

inserts: 23946
get: 24824

VC 2015帶有我知道的所有優化標志:

使用std :: unordered_map:

inserts: 7289
get: 1908

使用std :: map:

inserts: 19222 
get: 19711

我沒有使用GCC測試代碼,但我認為它可能與VC的性能相當,所以如果這是真的,那么GCC 4.9 std :: unordered_map它仍然被打破。

[編輯]

所以是的,正如有人在評論中所說,沒有理由認為GCC 4.9.x的性能可與VC性能相媲美。 當我有更改時,我將在GCC上測試代碼。

我的答案只是為其他答案建立某種知識基礎。

暫無
暫無

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

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