【问题标题】:Time efficiency on std::tr1::unordered_map::operator[]std::tr1::unordered_map::operator[] 上的时间效率
【发布时间】:2011-05-12 20:18:06
【问题描述】:

我正在优化 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 多秒。

我对 release 版本进行了分析器分析,分析器显示该行:footsteps[footstep] = d; 是主要的罪魁祸首,它运行了 447931 次,耗时约 20 秒。

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

unordered_mapoperator::[] 对我来说似乎是一个黑匣子。我不明白为什么要花这么长时间。这是一个32 位 应用程序。任何帮助表示赞赏。

【问题讨论】:

  • 您使用的是哪种 STL 实现?
  • 微软的 Visual C++,谢谢
  • 太多 ::&lt;&gt; 以及要被 C. 删除的演员表
  • Visual C++ 的哪个版本(包括应用的服务包)?我知道在 VC2008 SP1 中对 TR1 库进行了修复,因此如果您在没有服务包的情况下运行 VC2008,您可能想查看 SP 是否有帮助(或查看 VC2010 是否修复了问题)。
  • @Michael,感谢您的回复。我的 VC++ 更新了,版本 3.5 SP 1。

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


【解决方案1】:

在没有 SP1 的 VS 2008 中(但使用为您提供 TR1 库的功能包)tr1::unordered_map&lt;&gt; 的默认哈希函数仅考虑键值的低 32 位。至少这是通过我阅读 &lt;functional&gt; 标头中的 template&lt;class _Kty&gt; class hash::operator() 实现。

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

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

顺便说一句,看起来 VS 2010 在与 64 位整数一起使用时对 hash::operator() 进行了专门化,因此它将对所有 64 位进行散列 - 如果您使用的是 VS 2010,我的回答中的推测不应该申请。


更新:

经过一些测试,我确信这是问题所在(该问题在 VS 2008 SP1 中也存在)。您可以通过将编译器升级到具有更好的 64 位类型哈希函数的 VS 2010 来解决此问题,或者使用您自己的哈希函数自行处理。下面是我在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;

【讨论】:

  • 太棒了!它将我的运行时间减少到 4 秒。谢谢。
  • @Zeiga:你的分析器现在说什么? 4 秒听起来仍然很多(但也许它在代码的另一部分有意义)。
【解决方案2】:

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

【讨论】:

  • 感谢您的回复。但实际上,我的分析器结果基于发布版本,而不是调试版本。调试构建需要更长的时间。
  • 即使在发布版本中,默认也是#define _SECURE_SCL=1
  • 从 VS 2010 开始,_SECURE_SCL=0 在发布版本中。
【解决方案3】:

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

【讨论】:

  • 有可能。不幸的是,我的 Profiler 不够先进,无法查看内部发生的情况。
【解决方案4】:

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

【讨论】:

  • 请注意有嵌套循环 - 有 447931 个项目被添加到 unordered_map,而不是 946。
【解决方案5】:

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

Visual Studio 2010 修复了这个问题,默认情况下实际使发布版本快速

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-16
    • 1970-01-01
    相关资源
    最近更新 更多