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

时间:2014-01-21 10:21:41

标签: 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;                
    }    
}

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

while (IsBlocked() & & spinWait._SpinOnce())
{
//_YieldProcessor is called inside _SpinOnce
}

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

操作系统:Windows 7 x64

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

3 个答案:

答案 0 :(得分:8)

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

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

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

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

答案 1 :(得分:5)

互斥体无论如何都会在每个线程之间产生争用,但是如果你尝试使用的线程多于你拥有的核心,即使它们已经准备就绪,也不是所有线程都可以同时运行,所以它们需要保持停止状态。开始 - 称为上下文切换。

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

答案 2 :(得分:1)

您的问题是有8个线程存储到共享资源(不是加载,加载无法修改的共享资源是安全的,并且锁定是不必要的)

  1. 8个主题&gt;核心数意味着
    • 并非每个线程都可以在单个cpu中运行
    • 还有更多任务计划
  2. 互斥
    • 线程无法获取mutext会睡眠,并将此线程排队等待队列。(似乎windows中的互斥锁实现使用短旋转,然后将此线程排队等待队列,如果没有获取互斥锁?)< / LI>
  3. 写无锁算法很难,但在你的问题中,有一种方法。

    1. 如果你可以获得更多核心,那么
    2. 使用std::atomic<uint64_t>并删除互斥锁,默认情况下增加原子序数是原子的(没有特殊的内存模型)。
    3. 如果线程num不是常量,则将其更改为核心num,然后绑定它们

    4. #include <chrono>
      #include <thread>
      #include <memory>
      #include <atomic>
      #include <sstream>
      #include <iostream>
      
      using namespace std::chrono;
      
      void thread_loop(std::atomic<uint64_t>* counter)
      {
          while (true)
          {
                  (*counter)++;
          }
      }
      
      int main(int argc, char* argv[])
      {
      
          int threads = 9;
          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, &counter);
      
          std::cout << "Started " << threads << " threads.." << std::endl;
          while (1)
          {
              std::this_thread::sleep_for(seconds(1));
              std::cout << "Counter = " << counter.load() << std::endl;
          }
      }
      

      这可能更快。享受; - )