【问题标题】:Why did CRITICAL_SECTION performance become worse on Win8为什么 CRITICAL_SECTION 性能在 Win8 上变差了
【发布时间】:2019-02-09 17:53:19
【问题描述】:

在 Windows 8 及更高版本上,CRITICAL_SECTION 的性能似乎变差了。 (见下图)

测试非常简单:一些并发线程每个执行 300 万个锁来独占访问一个变量。您可以在问题的底部找到 C++ 程序。我在 Windows Vista、Windows 7、Windows 8、Windows 10(x64、VMWare、Intel Core i7-2600 3.40GHz)上运行测试。

结果如下图所示。 X 轴是并发线程数。 Y 轴是以秒为单位的经过时间(越低越好)。

我们可以看到:

  • SRWLock 所有平台的性能大致相同
  • CriticalSection 在 Windows 8 及更高版本上的 SRWL 性能相对较差

问题是:谁能解释一下为什么 CRITICAL_SECTION 性能在 Win8 及更高版本上变得更差?


一些注意事项:

  • 在真机上的结果几乎相同 - CS 在 Win8 及更高版本上比 std::mutex、std::recursive_mutex 和 SRWL 差得多。但是我没有机会在具有相同 CPU 的不同操作系统上运行测试。
  • Windows Vista 的std::mutex 实现基于CRITICAL_SECTION,但Win7 及更高版本std::mutex 的实现基于SWRL。对于 MSVS17 和 15 都是正确的(确保在安装 MSVC++ 时搜索 primitives.h 文件并查找 stl_critical_section_vistastl_critical_section_win7 类)这解释了 Win Vista 和其他系统上 std::mutex 性能之间的差异。
  • 正如在 cmets 中所说,std::mutex 是一个包装器,因此相对于 SRWL 的一些开销的可能解释可能是包装器代码引入的开销。

#include <chrono>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

#include <Windows.h>

const size_t T = 10;
const size_t N = 3000000;
volatile uint64_t var = 0;

const std::string sep = ";";

namespace WinApi
{
    class CriticalSection
    {
        CRITICAL_SECTION cs;
    public:
        CriticalSection() { InitializeCriticalSection(&cs); }
        ~CriticalSection() { DeleteCriticalSection(&cs); }
        void lock() { EnterCriticalSection(&cs); }
        void unlock() { LeaveCriticalSection(&cs); }
    };

    class SRWLock
    {
        SRWLOCK srw;
    public:
        SRWLock() { InitializeSRWLock(&srw); }
        void lock() { AcquireSRWLockExclusive(&srw); }
        void unlock() { ReleaseSRWLockExclusive(&srw); }
    };
}

template <class M>
void doLock(void *param)
{
    M &m = *static_cast<M*>(param);
    for (size_t n = 0; n < N; ++n)
    {
        m.lock();
        var += std::rand();
        m.unlock();
    }
}

template <class M>
void runTest(size_t threadCount)
{
    M m;
    std::vector<std::thread> thrs(threadCount);

    const auto start = std::chrono::system_clock::now();

    for (auto &t : thrs) t = std::thread(doLock<M>, &m);
    for (auto &t : thrs) t.join();

    const auto end = std::chrono::system_clock::now();

    const std::chrono::duration<double> diff = end - start;
    std::cout << diff.count() << sep;
}

template <class ...Args>
void runTests(size_t threadMax)
{
    {
        int dummy[] = { (std::cout << typeid(Args).name() << sep, 0)... };
        (void)dummy;
    }
    std::cout << std::endl;

    for (size_t n = 1; n <= threadMax; ++n)
    {
        {
            int dummy[] = { (runTest<Args>(n), 0)... };
            (void)dummy;
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::srand(time(NULL));
    runTests<std::mutex, WinApi::CriticalSection, WinApi::SRWLock>(T);
    return 0;
}

测试项目在 Microsoft Visual Studio 17 (15.8.2) 上构建为 Windows 控制台应用程序,具有以下设置:

  • MFC 的使用:在静态库中使用 MFC
  • Windows SDK 版本:10.0.17134.0
  • 平台工具集:Visual Studio 2017 (v141)
  • 优化:O2、Oi、Oy-、GL

【问题讨论】:

  • SRWLock和Critical Section在语义上有一些区别,请阅读:stackoverflow.com/questions/3498798/…
  • 我快速浏览了一下我的环境(Win7、VS2015)中的 std::mutex 实现——在 std::mutex 选择的任何操作系统原语之上都有一层间接(参见 _Mtx_storage + _Mtx_init_in_situ/etc 函数用于操作原语)。这可以解释一些观察到的性能下降。
  • @Rom098 它是线程之间的共享状态,其语义未由 C++ 标准定义。在试图分析多线程性能的代码中。
  • @Yakk-AdamNevraumont 一般来说,标准说 rand 不是线程安全的。您的意思是锁定互斥锁下的 rand() 调用不够线程安全吗?反正我用“var += 1”等做了实验,结果都是一样的。
  • 关于 srand/rand 的讨论,visual c++ 使用线程局部随机种子,因此有两个线程同时执行 rand() 不会相互干扰。但也要注意这意味着每个线程都需要调用 srand 来初始化 RNG。

标签: c++ c++11 winapi critical-section stdmutex


【解决方案1】:

Windows Critical Section - how to disable spinning completely 从 Windows 8 开始,Microsoft 更改了关键部分的默认行为的实现(文档中甚至没有一个字)(如果您使用 InitializeCriticalSection(&cs),您将在启用未记录的动态自旋调整算法的情况下进行自旋)。在这里查看我的评论:https://randomascii.wordpress.com/2012/06/05/in-praise-of-idleness/#comment-57420

对于您的测试,请尝试使用 InitializeCriticalSectionAndSpinCount(&cs,1) 而不是 InitializeCriticalSection(&cs)。这应该使它的行为与 Windows 7 有点相似,尽管在这方面还有很多其他变化。

【讨论】:

  • 您指的该领域的其他变化是什么?我知道历史上有很多变化,比如添加键控事件,或者从公平算法更改为不公平算法,但我不知道 Windows 7 和 Windows 10 之间的任何其他变化,除了这种自动旋转。
  • 实际上它在std::mutexCriticalSection 之间,如果我使用InitializeCriticalSectionAndSpinCount(&amp;cs,1),但它仍然更接近CriticalSection。所以你的解释看起来不像是根本原因。
  • @Rom098 解开这个谜的运气好吗?也许将此标记为答案。即使没有 100% 解释这一点,它也能很好地解释这个案子。
  • @quetzalcoatl 正如我之前提到的,这个答案似乎不是根本原因。最可能的原因是 Windows 更新出现问题。据我所知(但我不确定),安装了所有最新更新后,现在无法重现该问题。
  • @Rom098 实际上,这很可能是观察到的基准测试结果的根本原因。此示例中有一个真正的共享缓存线,因此任何延迟旋转和重新调度的算法都可以避免未来的几次缓存未命中,因为争用暂时消失了。而且重新调度的成本与 Vista 相比降低了很多,所以在 spin 上烧掉内存事务已经完全不值得了。
猜你喜欢
  • 1970-01-01
  • 2015-06-04
  • 2020-01-01
  • 1970-01-01
  • 2020-07-30
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 1970-01-01
相关资源
最近更新 更多