简单的多线程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超线程)
答案 0 :(得分:8)
使用8个线程,您的代码正在旋转,但是在没有CPU必须在线程丢失其时间片之前暂停线程的情况下获取锁定。
随着您添加越来越多的线程,争用级别会增加,因此线程无法在其时间片内获取锁定的可能性。当发生这种情况时,线程被挂起并且上下文开关发生在另一个线程上,CPU将检查该线程是否可以被唤醒。
所有这些切换,挂起和唤醒都需要从用户模式转换到内核模式,这是一项昂贵的操作,因此性能会受到很大影响。
要改进一些事情,要么减少争用锁定的线程数量,要么增加可用内核数量。在您的示例中,您使用的是std::atomic
数字,因此您无需锁定以便在其上调用++
,因为它已经是线程安全的。
答案 1 :(得分:5)
互斥体无论如何都会在每个线程之间产生争用,但是如果你尝试使用的线程多于你拥有的核心,即使它们已经准备就绪,也不是所有线程都可以同时运行,所以它们需要保持停止状态。开始 - 称为上下文切换。
您可以“解决”这个问题的唯一方法是使用更少的线程或获得更多内核。
答案 2 :(得分:1)
您的问题是有8个线程存储到共享资源(不是加载,加载无法修改的共享资源是安全的,并且锁定是不必要的)
写无锁算法很难,但在你的问题中,有一种方法。
std::atomic<uint64_t>
并删除互斥锁,默认情况下增加原子序数是原子的(没有特殊的内存模型)。#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;
}
}
这可能更快。享受; - )