VC++:性能下降x20当线程多于cpus但不在g ++下

Gab*_*iMe 9 c++ multithreading visual-c++ c++11 visual-studio-2013

简单的多线程c ++ 11程序,其中所有线程在紧密循环中锁定相同的互斥锁.

当它使用8个线程(作为逻辑cpus的数量)时,它可以达到500万次/秒

但只添加一个额外的线程 - 性能下降到200,000 /秒!

编辑:

在g ++ 4.8.2(ubuntu x64)下:即使有100个线程也没有性能下降!(并且是性能的两倍以上,但这是另一个故事) - 所以这确实是VC++互斥实现的特定问题

我使用以下代码(Windows 7 x64)复制它:

#include <chrono>
#include <thread>
#include <memory>
#include <mutex>
#include <atomic>
#include <sstream>
#include <iostream>

using namespace std::chrono;

void thread_loop(std::mutex* mutex, std::atomic_uint64_t* counter)
{
    while (true)
    {
        std::unique_lock<std::mutex> ul(*mutex);        
        counter->operator++();                    
    }        
}

int _tmain(int argc, _TCHAR* argv[])
{    

    int threads = 9;
    std::mutex mutex;
    std::atomic_uint64_t counter = 0;

    std::cout << "Starting " << threads << " threads.." << std::endl;
    for (int i = 0; i < threads; ++i)
        new std::thread(&thread_loop, &mutex, &counter);

    std::cout << "Started " << threads << " threads.." << std::endl;
    while (1)
    {   
        counter = 0;
        std::this_thread::sleep_for(seconds(1));        
        std::cout << "Counter = " << counter.load() << std::endl;                
    }    
}
Run Code Online (Sandbox Code Playgroud)

VS 2013剖析器告诉我,大部分时间(95.7%)都是在紧密循环中浪费的(rtlocks.cpp中的第697行):

while (IsBlocked() & & spinWait._SpinOnce())
{
//_YieldProcessor is called inside _SpinOnce
}
Run Code Online (Sandbox Code Playgroud)

可能是什么原因?如何改进?

操作系统:Windows 7 x64

CPU:i7 3770 4核(x2超线程)

Sea*_*ean 8

使用8个线程,您的代码正在旋转,但是在没有CPU必须在线程丢失其时间片之前挂起线程的情况下获取锁定.

随着您添加越来越多的线程,争用级别会增加,因此线程无法在其时间片内获取锁定.当发生这种情况时,线程被挂起并且上下文开关发生在另一个线程中,CPU将检查该线程是否可以被唤醒.

所有这些切换,挂起和唤醒都需要从用户模式转换到内核模式,这是一项昂贵的操作,因此性能会受到很大影响.

要改进一些事情,要么减少争用锁定的线程数量,要么增加可用内核数量.在您的示例中,您使用的是std::atomic数字,因此您无需锁定以便对其进行调用++,因为它已经是线程安全的.

  • @Damon说的是什么.当准备好的线程数大于核心数时,自旋锁表现很差,而当线程同步开销的"有用工作"比例非常小时,自旋锁表现非常糟糕,如OP情况.CPU周期和内存带宽在尝试获取由准备好但未运行的线程保持的锁时基本上是生气的.如果互斥锁的spinlock-count设置为零,性能将提高,因此如果线程无法获得锁定,则允许立即阻塞. (6认同)
  • @GabiMe:你认为当一个进程持有一个锁定而另一个进程被锁定并开始旋转时会发生什么?当预定进程尝试获取锁定时,旋转线程等待的条件不可能成立.这就是为什么用比线程更多的线程进行旋转的原因就是死亡. (4认同)

doc*_*ove 5

互斥锁给每个线程之间的竞争,无论如何,但是如果您尝试使用多个线程比你有核心,即使他们已经准备好,不是所有的人都能同时运行,因此他们将需要保持停止和启动 - 已知作为上下文切换.

您可以"解决"这个问题的唯一方法是使用更少的线程或获得更多内核.

  • '如果你尝试使用比你有核心更多的READY线程'.这个答案在将此效应分配给上下文切换开销而不是低效使用锁定方案时会产生误导/错误,但是,正如"多线程"中常见的那样,它已经累积了最高票数. (2认同)