多线程中的 std::map 奇怪的资源争用
Posted
技术标签:
【中文标题】多线程中的 std::map 奇怪的资源争用【英文标题】:std::map strange resource contention in multithreading 【发布时间】:2013-02-26 12:39:32 【问题描述】:我对 std::map(或 std::set,它们在这种情况下的行为似乎相同)有一个奇怪的行为。 我可能对这应该如何工作有一个严重的误解。 我正在使用 VS2010 SP1。
以这个函数为例:
extern time_t g_nElapsed;
UINT Thread(LPVOID _param)
UINT nRuns = (UINT)_param;
for(UINT i=0; i<nRuns; ++i)
time_t _1 = time(NULL);
std::set<UINT> cRandomSet;
cRandomSet.insert(1);
cRandomSet.insert(2);
cRandomSet.insert(3);
cRandomSet.insert(4);
g_nElapsed += (time(NULL) - _1);
return 0;
现在,如果我运行 8 个线程,每个线程进行 100,000 次迭代,g_nElapsed 将大约为 40 秒。 如果我以 800,000 次迭代运行 1 个线程,g_nElapsed 将约为 5 秒。 我的印象是 g_nElapsed 对于任何合理数量的线程都应该大致相同。 可以这么说......处理器使用率随着线程数的增加而增加,即使工作保持不变。 但是,似乎与集合的某种资源争用会导致运行时间增加。 但为什么?这是线程本地...
我确定这是一个简单的误解和简单的解决方法,但 我不太确定问题出在哪里。
以下代码没有表现出这种行为:
extern time_t g_nElapsed;
UINT Thread(LPVOID _param)
UINT nRuns = (UINT)_param;
for(UINT i=0; i<nRuns; ++i)
time_t _1 = time(NULL);
UINT n[4];
n[0] = 1;
n[1] = 1;
n[2] = 1;
n[3] = 1;
g_nElapsed += (time(NULL) - _1);
return 0;
【问题讨论】:
它在创建/销毁对象cRandomSet
时的分配器锁。另外,每个cRandomSet.insert
都可以调用new,所以它的锁也可以。
内存分配的(不那么)美好的一面......
【参考方案1】:
您正在创建和销毁许多容器,每个容器都使用operator new
分配内存。在许多系统上,这需要同步来管理像您这样的典型小分配上分配的空闲内存。所以你可能会在那里招致相当多的线程间争用。
您可以尝试不同的分配器,例如 tcmalloc (http://goog-perftools.sourceforge.net/doc/tcmalloc.html)。它是专门为解决这个问题而设计的。
另一种方法是使用对象池或其他分配策略来完全避免使用标准分配机制。这将需要一些代码更改,而使用 tcmalloc 则不需要。
【讨论】:
分配器的对象池实现只需要很少的代码更改,除了编写 c 的分配器。 当然,但很少比没有更重要!我并不是说不要使用对象池或其他什么,它们也是一个好主意。 你是对的。使用 UINT *nnn = new UINT[5] 实现第二个变体并删除导致与映射和向量相同的行为。我不敢相信我从来没有考虑过 new/delete 是这里的竞争资源。我将检查 tcmalloc 库。 对象池在这里似乎是错误的解决方案,因为在实际程序中,映射是作为两个映射的联合创建的(以消除重复),因此对于对象池,我仍然会有很多争用相信。 我想补充一下,我已经在英特尔的 TBB 库中选择了可扩展分配器。这似乎会带来显着的性能提升!以上是关于多线程中的 std::map 奇怪的资源争用的主要内容,如果未能解决你的问题,请参考以下文章