std :: tr1 :: unordered_map :: operator []的时间效率

时间:2011-05-12 20:18:07

标签: c++ visual-c++ stl hash optimization

我正在优化Visual Studio 2008 SP1中的一段代码。知道unorder_map在插入/删除/查找常量时间时非常棒,所以我使用unordered_map作为我的主要数据结构来优化代码。请查看以下代码。

....
    typedef std::tr1::unordered_map <__int64, int> umap_id;
    const int text1_length = text1.GetLength();
    const int text2_length = text2.GetLength();
    const int max_d = text1_length + text2_length - 1;
    const bool doubleEnd = (64 < max_d);
    vector<set<pair<int, int> > > v_map1;
    vector<set<pair<int, int> > > v_map2;
    vector<int> v1(2 *max_d, 0);
    vector<int> v2(2 *max_d, 0);

    int x, y;
    __int64 footstep;
    umap_id footsteps(max_d * 2);
    bool done = false;
    const bool forward = ((text1_length + text2_length) % 2 == 1);

    for (int d = 0; d < max_d; ++d)
    {
        // Walk forward path one step
        v_map1.push_back(set<pair<int, int> >());
        for (int k = -d; k <= d; k += 2)
        {
            if (k == -d || (k != d && v1[k - 1 + max_d] < v1[k + 1 + max_d]))
                x = v1[k + 1 + max_d];
            else
                x = v1[k - 1 + max_d] + 1;
            y = x - k;

            if (doubleEnd)
            {
                footstep = (__int64) ((__int64)x << 32 | y);
                if (!forward)
                    footsteps[footstep] = d;
                else if (footsteps.find(footstep) != footsteps.end())
                    done = true;
            }
            ....
        }
    }
....

但事实证明它仍然很慢。鉴于我的输入相对较小(max_d = 946),它运行时间超过20秒。

我对发布构建进行了分析器分析,并且分析器显示该行:footsteps[footstep] = d;是主要的罪魁祸首,运行447931次并花了大约20秒。

请注意,同一个循环体中还有另一行代码:else if (footsteps.find(footstep) != footsteps.end())执行相同的次数(即447931次)但耗费的秒数要少得多。

operator::[]的{​​{1}}对我来说似乎是一个黑盒子。我无法弄清楚为什么需要这么长时间。这是一个 32位应用程序。任何帮助表示赞赏。

5 个答案:

答案 0 :(得分:2)

在调试版本中,随Visual Studio一起使用的STL大量使用迭代器检查和小型嵌套函数,这些函数在发布版本中全部内联。这就是为什么使用STL的调试代码与发布代码相比非常慢。

答案 1 :(得分:2)

在没有SP1的VS 2008中(但是使用为您提供TR1库的Feature Pack),tr1::unordered_map<>的默认哈希函数仅考虑键值的低32位。至少这是我在template<class _Kty> class hash::operator()标题中阅读<functional>实现的内容。

密钥使用的footstep变量使用y计算的任何变量作为其低32位 - y中有足够的变化,它可以自己创建一个好的哈希值(我可以不知道计算y的代码在做什么)?如果没有,您可能会将更多项目放入特定的哈希桶中,并产生太多的冲突。

如果是这种情况,您可能需要考虑提供自己的哈希函数。

顺便说一句,看起来VS 2010在使用64位整数时对hash::operator()有专门化,所以它会散列所有64位 - 如果你使用的是VS 2010,我的回答中的推测不应该适用。


更新

经过一些测试,我确信这是问题(VS 2008 SP1中也存在这个问题)。您可以通过将编译器升级到VS 2010来解决此问题,VS 2010具有更好的64位类型的散列函数,或者使用您自己的散列函数来自行处理。以下是我在VS2008中快速测试的一个,它似乎有效:

class hash_int64
    : public unary_function<__int64, size_t>
{
public:
    typedef __int64 key_t;
    typedef unsigned int half_t;

    size_t operator()(const key_t& key) const
    {   
        // split the 64-bit key into 32-bit halfs, hash each
        // then combine them
        half_t x = (half_t) (key & 0xffffffffUL);
        half_t y = (half_t) (((unsigned __int64) key) >> 32);

        return (hash<half_t>()(x) ^ hash<half_t>()(y));
    }
};

然后将typedef更改为:

typedef std::tr1::unordered_map <__int64, int, hash_int64> umap_id;

答案 2 :(得分:0)

可能你会遇到很多碰撞。如果使用哈希函数实现unordered_map来创建索引并且您遇到大量冲突,则必须遍历列表才能到达您的项目。这可能是一个原因,但我从未查看过unordered_map实现。

答案 3 :(得分:0)

已知哈希映射具有相当高的常量开销。只有946个元素(基本上是免费的比较运算符)应该使用std::map的O(log(n))查找。但是,operator[]没有理由比find()花费更多时间,除非存在实施错误。

答案 4 :(得分:0)

在Visual Studio 2005和2008中,您应手动设置_SECURE_SCL=0。它默认启用,即使在发布版本中,也会增加大量的运行时检查,在某些情况下这可能会非常昂贵。

Visual Studio 2010修复此问题,默认为实际发布版本快速

除此之外,可能值得尝试用普通的std::map替换数据结构。当然,只要在32位版本中使用64位整数键也是非最佳的。

定位x64可能会明显改善,但是如果你坚持使用32位,你可以考虑是否可以用双精度替换整数键,因为CPU可以原生地处理这些(我不知道是什么虽然看起来像双打的默认散列函数,但它总体上可能会更慢,但至少可能值得测试)