如果在调试配置

时间:2017-10-18 01:03:02

标签: c++ performance visual-studio-2010 hashmap

我有一个使用工具集V10的托管VS2010 C ++项目。我无法弄清楚的是,如果我使用Debug Configuration编译我的项目,hash_map析构函数会非常慢。 hash_map.clear()稍快一点,但同样痛苦。

注意:这在VS2015 + Win10上无法再现。最有可能是VS2010问题。

我环顾网络,但是,没有什么可以解释我得到的东西。

1)我检查了_NO_DEBUG_HEAP = 1环境设置。这对我不起作用,因为我没有通过VS进行调试。我只是在调试配置中编译代码并在没有调试器的情况下运行它。

2)这不是关于插入大量数据。插入很好。这只是简单地从hash_map中清除数据。

3)我想如果我在C ++代码生成设置下关闭C ++ Exception,我可以解决问题,但情况并非如此。

如果我在Release Configuration中编译代码,则销毁是即时的。如果我在Debug Configuration中编译代码,那么破坏就像5分钟或更长时间取决于数据有多大。

我确信这只是某种我需要纠正的C ++项目设置。任何人都知道如何解决这个问题?

总结一下,我的项目是VS2010托管C ++(在C ++和托管C#对象之间混合),工具集是v10。当我使用Debug Configuration编译代码时,需要5分钟来销毁hash_map(比插入数据本身慢)。当我使用Release Configuration编译代码时,它是即时的。

我该如何解决这个问题?谢谢。

这是我使用VS2010制作的新项目的完整代码。 我花了不到5秒的时间插入这些物品。 myMap.clear()需要202秒。 myMap析构函数需要280秒。

我做了什么。

1)使用VS2010创建新的C ++ Console应用程序...

2)设置配置属性...

2.1)一般>公共语言运行时支持>没有支持......

2.2)一般>字符集>使用多字节...

2.3)C / C ++>一般>公共语言运行时支持>是/ clr ......

2.4)C / C ++>一般>调试信息格式>程序数据库/ Zi ...

2.5)C / C ++>代码生成>启用最小重建>不/ Gm -...

2.6)C / C ++>代码生成>启用C ++例外>是的SEH / EHa ......

   Use Koby code below.

更多更新:

这似乎也依赖于硬件? 30K到40K之间存在很大的插入性能差异。插入卡片在32K处停留一段时间并快速插入其余物品。当发生这种情况时,map.clear()或析构函数变得非常慢。

不同机器的初始容量可能不同?我在64位Windows7上使用4GB内存的VM。该程序在Debug配置中为32位。

Koby code result. Running compiled exe without VS2010.
Testing with 30K items:

TestClear()
Insertion: 0.163s
Clear: 0.075s

TestDestructor()
Insertion: 0.162s
Destruction: 4.262s

Testing with 40K items:

TestClear()
Insertion: 4.552s
Clear: 197.363s

TestDestructor()
Insertion: 4.49s
I gave up since destructor is much slower in 30K result..

在VS2015和64位Win10上进行测试

Testing with 4M items:

TestClear()
Insertion: 8.988s
Clear: 0.878s

TestDestructor()
Insertion: 9.669s
Destruction: 0.869s

结论: VS2010最有可能出现问题。我将此标记为已解决,因为VS2010太旧了。我会尝试将整个解决方案升级到更高的VS.谢谢Koby。

1 个答案:

答案 0 :(得分:4)

TL; DR: std::hash_map不是正确的工作工具。这是MSVC多年前弃用的non-standard extension。阅读std::unordered_map,它应该符合您的一般性能要求。另请参阅this

我在撰写本文时使用的是VS 2017 15.3.5,因此我的std库标题副本是合理的最新版本。我不再拥有VS 2010头文件的副本,因此我的答案可能不完全适用于您的版本。如果可能的话,你应该真的升级。

首先让我们分析hash_map.clear()

void clear() _NOEXCEPT
    {   // erase all
    _List.clear();
    _Init();
    }

_Liststd::list。清除它需要在内存周围摧毁对象,直到所有节点都被清除。虽然链接列表是必要的,但它的性能却很糟糕。链接列表通常会导致许多cache misses。这是问题的主要原因。

现在让我们来看看_Init()

void _Init(size_type _Buckets = _Min_buckets)
    {   // initialize hash table with _Buckets buckets, leave list alone
    _Vec.reserve(2 * _Buckets); // avoid curdling _Vec if exception occurs
    _Vec.assign(2 * _Buckets, _Unchecked_end());
    _Mask = _Buckets - 1;
    _Maxidx = _Buckets;
    }

_Vecstd::vector的迭代器。_Min_buckets在我的副本中是8,而reserve()只是增长,从不收缩,所以我们可以假设重新分配不是在大多数时间发生(至少从清楚)。但是,通过调用,它执行多个分支,可能的大量析构函数调用和8个迭代器分配。这可能看起来不多,但可以快速加起来。

如果不编写自己的哈希映射或使用备用实现,就无法做到这一点。幸运的是,该标准为我们提供了std::unordered_map

<强>更新
我在VS 2015和VS 2017中运行了您的示例,更改了您提到的所有设置,以尝试重现慢速方案。在所有情况下,它都很快完成,从不超过一秒钟。

您的示例不会独立衡量clear()或销毁时间。这是一个针对C ++ / CLI(托管C ++的继承者)的修改版本。如果它没有为VS 2010编译,请修改main

的签名
#include "stdafx.h"
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
#include <hash_map>
#include <iostream>
#include <sstream>
#include <time.h>

using namespace System;

namespace Test
{
    typedef stdext::hash_map<unsigned int, float> Collection;
    typedef std::pair<unsigned int, float> Pair;

    const int Iterations = 40000;
    const int Multiple = 1000;
    const float Increment = 0.004;

    clock_t startTime = 0;

    std::string ToDisplayString(double count)
    {
        const double Million = 1000000;
        const double Thousand = 1000;

        std::stringstream stream;

        if (count > Million) {
            stream << (count / Million) << "M";
        } else if (count > Thousand) {
            stream << (count / Thousand) << "K";
        } else {
            stream << count;
        }
        return stream.str();
    }

    void ResetClock()
    {
        startTime = clock();
    }
    double GetElapsedSeconds()
    {
        return (clock() - startTime) / (double)CLOCKS_PER_SEC;
    }

    void ClearSample()
    {
        std::cout << "TestClear()\n";

        Collection map;
        double duration;
        ResetClock();

        for (int i = 0; i < Iterations; i++) {
            if (i % Multiple == 0) {
                if (i != 0) {
                    std::cout << ' ';
                }
                std::cout << i;
            }
            map.insert(Pair(i, i + Increment));
        }

        duration = GetElapsedSeconds();
        std::cout << "\nInsertion: " << duration << "s";

        ResetClock();
        map.clear();
        duration = GetElapsedSeconds();
        std::cout << "\nClear: " << duration << "s";
    }

    void DestructorSample()
    {
        std::cout << "TestDestructor()\n";

        double duration;
        {
            // Moved to a block so we can time destruction.
            Collection map;
            ResetClock();

            for (int i = 0; i < Iterations; i++) {
                if (i % Multiple == 0) {
                    if (i != 0) {
                        std::cout << ' ';
                    }
                    std::cout << i;
                }

                map.insert(Pair(i, i + Increment));
            }

            duration = GetElapsedSeconds();
            std::cout << "\nInsertion: " << duration << "s";
            ResetClock();
        }
        duration = GetElapsedSeconds();
        std::cout << "\nDestruction: " << duration << "s";
    }
}

int main(array<String^>^ args)
{
    std::cout << "Testing with " << Test::ToDisplayString(Test::Iterations) << " items:\n\n";

    Test::ClearSample();
    std::cout << "\n\n";
    Test::DestructorSample();

    std::cin.get();
    return 0;
}

这是我多次测量的平均时间:
ClearSample()
插入:0.45秒
清除:0.021s

DestructorSample()
插入:0.4s
毁灭:0.013s