簡體   English   中英

為什么迭代器調試在調試版本中會降低std :: unordered_map 200x的速度?

[英]Why does iterator debugging slow std::unordered_map 200x in debug builds?

我知道代碼會慢一點,但為什么這么多呢? 我如何編碼以避免這種減速?

std :: unordered_map在內部使用其他容器,這些容器使用迭代器。 構建調試時,默認情況下_ITERATOR_DEBUG_LEVEL = 2。 這將打開迭代器調試 有時我的代碼不會受到太大影響,有時它的運行速度非常慢。

我可以通過在項目屬性>> C ++ >>預處理器>>預處理器定義中設置_ITERATOR_DEBUG_LEVEL = 0來加速我的示例。 但正如這個鏈接所暗示的那樣,我不能在我的真實項目中這樣做。 在我的例子中,我與MSVCMRTD.lib發生沖突,其中包含使用_ITERATOR_DEBUG_LEVEL = 2構建的std :: basic_string。 我知道我可以通過靜態鏈接到CRT解決問題。 但我不願意,如果我可以修復代碼,所以問題不會出現。

我可以做出改善以改善這種情況。 但我只是在嘗試解決問題,而不理解它們的工作原理。 例如,前1000個插入件全速工作。 但是如果我將O_BYTE_SIZE更改為1,則第一次插入與其他所有內容一樣慢。 這看起來像一個小小的改變(不一定是一個很好的改變。)

這個這個也有所啟發,但不回答我的問題。

我正在使用Visual Studio 2010(這是遺留代碼。)我創建了一個Win32控制台應用程序並添加了此代碼。

Main.cpp的

#include "stdafx.h"


#include "OString.h"
#include "OTHashMap.h"

#include <cstdio>
#include <ctime>
#include <iostream>

// Hash and equal operators for map
class CRhashKey {
public:
   inline unsigned long operator() (const OString* a) const { return a->hash(); }
};

class CReqKey {
public:
    inline bool operator() (const OString& x, const OString& y) const { return strcmp(x.data(),y.data()) != 0; }
    inline bool operator() (const OString* x, const OString& y) const { return operator()(*x,y); }
    inline bool operator() (const OString& x, const OString* y) const { return operator()(x,*y); }
    inline bool operator() (const OString* x, const OString* y) const { return operator()(*x,*y); }
};


int _tmain(int argc, _TCHAR* argv[])
{
    const int CR_SIZE = 1020007;

    CRhashKey h;
    OTPtrHashMap2<OString, int, CRhashKey, CReqKey> *code_map = 
        new OTPtrHashMap2 <OString, int, CRhashKey, CReqKey>(h, CR_SIZE);

    const clock_t begin_time = clock();

    for (int i=1; i<=1000000; ++i)
    {
        char key[10];
        sprintf(key, "%d", i);

        code_map->insert(new OString(key), new int(i));

        //// Check hash values
        //OString key2(key);
        //std::cout << i << "\t" << key2.hash() << std::endl;

        // Check timing
        if ((i % 100) == 0)
        {
            std::cout << i << "\t" << float(clock() - begin_time) / CLOCKS_PER_SEC << std::endl;
        }
    }

    std::cout << "Press enter to exit" << std::endl;
    char buf[256];
    std::cin.getline(buf, 256);

    return 0;
}

OTHashMap.h

#pragma once

#include <fstream>
#include <unordered_map>    

template <class K, class T, class H, class EQ>
class OTPtrHashMap2
{
    typedef typename std::unordered_map<K*,T*,H,EQ>                     OTPTRHASHMAP_INTERNAL_CONTAINER;
    typedef typename OTPTRHASHMAP_INTERNAL_CONTAINER::iterator          OTPTRHASHMAP_INTERNAL_ITERATOR;

public:
    OTPtrHashMap2(const H& h, size_t defaultCapacity) : _hashMap(defaultCapacity, h) {}

    bool insert(K* key, T* val)
    {
        std::pair<OTPTRHASHMAP_INTERNAL_ITERATOR,T> retVal = _hashMap.insert(std::make_pair<K*,T*>(key, val));
        return retVal.second != NULL;
    }

    OTPTRHASHMAP_INTERNAL_CONTAINER _hashMap;

private:
};

OString.h

#pragma once

#include <string>

class OString
{
public:
    OString(const std::string& s) : _string (s) { } 
    ~OString(void) {}

    static unsigned hash(const OString& s) { return unsigned (s.hash()); }
    unsigned long hash() const
    {
        unsigned hv = static_cast<unsigned>(length());
        size_t i = length() * sizeof(char) / sizeof(unsigned);
        const char * p = data();
        while (i--) {
            unsigned tmp;
            memcpy(&tmp, p, sizeof(unsigned));
            hashmash(hv, tmp);
            p = p + sizeof(unsigned);
        } 
        if ((i = length() * sizeof(char) % sizeof(unsigned)) != 0)  {
            unsigned h = 0;
            const char* c = reinterpret_cast<const char*>(p);
            while (i--)
            {
                h = ((h << O_BYTE_SIZE*sizeof(char)) | *c++);
            }
            hashmash(hv, h);
        }
        return hv; 
    }

    const char* data() const { return _string.c_str(); }
    size_t length() const    { return _string.length(); }


private:
    std::string _string;

    //static const unsigned O_BYTE_SIZE = 1;
    static const unsigned O_BYTE_SIZE = 8;
    static const unsigned O_CHASH_SHIFT = 5;

    inline void hashmash(unsigned& hash, unsigned chars) const
    {
        hash = (chars ^
                ((hash << O_CHASH_SHIFT) |
                 (hash >> (O_BYTE_SIZE*sizeof(unsigned) - O_CHASH_SHIFT))));
    }
};

我找到了足夠的答案。 碰撞是減速的根源。

編輯2 : - 另一個解決方法是在main.cpp中的#include周圍添加它 -

// Iterator debug checking makes the Microsoft implementation of std containers 
// *very* slow in debug builds for large containers. It must only be undefed around 
// STL includes. Otherwise we get linker errors from the debug C runtime library, 
// which was built with _ITERATOR_DEBUG_LEVEL set to 2. 
#ifdef _DEBUG
#undef _ITERATOR_DEBUG_LEVEL
#endif

#include <unordered_map>

#ifdef _DEBUG
#define _ITERATOR_DEBUG_LEVEL 2
#endif

編輯 : - 修復程序切換到boost :: unordered_map。 -

std :: unordered_map在<unordered_map>中定義。 它繼承自_Hash,在<xhash>中定義。

_Hash包含這個(高度縮寫)

template<...> 
class _Hash
{
    typedef list<typename _Traits::value_type, ...> _Mylist;
    typedef vector<iterator, ... > _Myvec;

    _Mylist _List;  // list of elements, must initialize before _Vec
    _Myvec _Vec;    // vector of list iterators, begin() then end()-1
};

所有值都存儲在_List中。

_Vec是_List中迭代器的向量。 它將_List分為多個桶。 _Vec有一個到每個桶的開頭和結尾的迭代器。 因此,如果映射具有1M桶(不同的鍵哈希),則_Vec具有2M迭代器。

將鍵/值對插入映射時,通常會創建一個新存儲桶。 該值將被推送到列表的開頭。 密鑰的哈希是_Vec中放置兩個新迭代器的位置。 這很快,因為它們指向列表的開頭。

如果存儲桶已存在,則必須在_List中的現有值旁邊插入新值。 這需要在列表中間插入一個項目。 必須更新現有迭代器。 顯然,在啟用迭代器調試時,這需要大量工作。 代碼在<list>中,但我沒有單步執行它。


為了了解工作量,我使用了一些使用起來很糟糕的無意義哈希函數,但在插入時會產生大量碰撞或幾次碰撞。

添加到OString.h

static unsigned hv2;

// Never collides. Always uses the next int as the hash
unsigned long hash2() const
{
    return ++hv2;
}

// Almost never collides. Almost always gets the next int. 
// Gets the same int 1 in 200 times. 
unsigned long hash3() const
{
    ++hv2;
    unsigned long lv = (hv2*200UL)/201UL;
    return (unsigned)lv;
}

// A best practice hash
unsigned long hash4() const
{
    std::hash<std::string> hasher;
    return hasher(_string);
}

// Always collides. Everything into bucket 0. 
unsigned long hash5() const
{
    return 0;
}

添加到main.cpp

// Hash and equal operators for map
class CRhashKey {
public:
   //inline unsigned long operator() (const OString* a) const { return a->hash(); }
   //inline unsigned long operator() (const OString* a) const { return a->hash2(); }
   //inline unsigned long operator() (const OString* a) const { return a->hash3(); }
   //inline unsigned long operator() (const OString* a) const { return a->hash4(); }
   inline unsigned long operator() (const OString* a) const { return a->hash5(); }
};

unsigned OString::hv2 = 0;

結果是戲劇性的。 沒有現實的哈希值可行。

  • hash2 - 永不碰撞 - 在15.3秒內插入1M次
  • hash3 - 幾乎從不 - 在206秒內插入1M
  • hash4 - 最佳實踐 - 在132秒內插入100k,隨着碰撞變得更頻繁而變慢。 1M插入需要> 1小時
  • hash5 - 始終發生沖突 - 在48秒內插入1k,或在~13小時內插入1M次插入

我的選擇是

  • 正如Retired Ninja建議的那樣,發布構建,調試符號,優化
  • 靜態鏈接到MSVCMRTD所以我可以關閉_ITERATOR_DEBUG_LEVEL。 還解決了一些其他類似的問題。
  • 從unordered_map更改為已排序的向量。
  • 還有別的。 建議歡迎。

暫無
暫無

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

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