Ale*_*iev 13 c++ visual-c++ stdmutex
在 Visual Studio 2022 中以发布模式运行以下命令:
#include <chrono>
#include <mutex>
#include <shared_mutex>
#include <iostream>
std::mutex mx;
std::shared_mutex smx;
constexpr int N = 100'000'000;
int main()
{
auto t1 = std::chrono::steady_clock::now();
for (int i = 0; i != N; i++)
{
std::unique_lock<std::mutex> l{ mx };
}
auto t2 = std::chrono::steady_clock::now();
for (int i = 0; i != N; i++)
{
std::unique_lock<std::shared_mutex> l{ smx };
}
auto t3 = std::chrono::steady_clock::now();
auto d1 = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
auto d2 = std::chrono::duration_cast<std::chrono::duration<double>>(t3 - t2);
std::cout << "mutex " << d1.count() << "s; shared_mutex " << d2.count() << "s\n";
std::cout << "mutex " << sizeof(mx) << " bytes; shared_mutex " << sizeof(smx) << " bytes \n";
}
Run Code Online (Sandbox Code Playgroud)
输出如下:
mutex 2.01147s; shared_mutex 1.32065s
mutex 80 bytes; shared_mutex 8 bytes
Run Code Online (Sandbox Code Playgroud)
为什么这样?
出乎意料的是,功能更丰富的速度std::shared_mutex比 更快std::mutex,严格来说,它只是其功能的子集。
Ale*_*iev 26
TL;DR:不幸的是,向后兼容性和 ABI 兼容性问题的结合会使情况变得std::mutex很糟糕,直到下一次 ABI 中断为止。奥托,std::shared_mutex很好。
一个不错的实现std::mutex会尝试使用原子操作来获取锁,如果繁忙,可能会尝试在读循环中旋转(有些pause在 x86 上),最终将诉诸操作系统等待。
有几种方法可以实现这样的std::mutex:
当然,第一种方式更容易实现,更易于调试,更健壮。所以这似乎是一条必经之路。候选 API 是:
CRITICAL_SECTION蜜蜂。递归互斥体,缺少静态初始化程序并且需要显式销毁SRWLOCK。具有静态初始化程序并且不需要显式销毁的非递归共享互斥体WaitOnAddress。等待特定变量更改的 API,类似于 Linux futex。这些原语有操作系统版本要求:
CRITICAL_SECTION我认为自 Windows 95 以来就存在,虽然Windows 9x 中不存在,但自 Windows Vista 以来就添加了TryEnterCriticalSection使用CRITICAL_SECTIONwith 的功能。CONDITION_VARIABLECONDITION_VARIABLESRWLOCK从Windows Vista开始就存在,但是TryAcquireSRWLockExclusive从Windows 7开始就存在,所以只能直接实现std::mutex在Windows 7中启动。WaitOnAddress从 Windows 8 开始添加。添加的时候std::mutex,Windows XP需要Visual Studio C++库的支持,所以就用自己的服务来实现。事实上,std::mutex其他同步工作都委托给了 ConCRT(并发运行时)
对于 Visual Studio 2015,实现已切换为使用最佳可用机制,即SRWLOCK从 Windows 7 开始,并CRITICAL_SECTION在 Windows Vista 中声明。ConCRT 被证明不是最好的机制,但它仍然被用于 Windows XP 和 2003。多态性是通过将具有虚拟函数的新类放置到由std::mutex和其他原语提供的缓冲区中来实现的。
std::mutex请注意,此实现打破了to be 的要求constexpr,因为运行时检测、放置 new 以及 Window 7 之前的实现无法仅具有静态初始化程序。
随着时间的推移,VS 2019 中最终放弃了对 Windows XP 的支持,VS 2022 中放弃了对 Windows Vista 的支持,进行更改是为了避免使用 ConCRT,计划进行更改是为了避免 SRWLOCK 的运行时检测(披露:我'我贡献了这些 PR)。仍然由于 VS 2015 和 VS 2022 的 ABI 兼容性,不可能简化std::mutex实现来避免所有这些将类与虚拟函数放在一起的情况。
更可悲的是,虽然SRWLOCK有静态初始化程序,但所述兼容性阻止了constexpr互斥体:我们必须在那里放置新的实现。不可能避免放置 new 并在内部进行构造的实现std::mutex,因为std::mutex它必须是标准布局类(请参阅为什么 std::mutex 是标准布局类?)。
所以大小开销来自ConCRT互斥体的大小。
运行时开销来自调用链:
SRWLOCK基于 get 的实现由于标准库 DLL 是使用/guard:cf.
部分运行时开销是由于std::mutex填充所有权计数和锁定线程造成的。尽管 . 不需要此信息SRWLOCK。这是由于与 共享内部结构recursive_mutex。额外的信息可能对调试有帮助,但填充它确实需要时间。
std::shared_mutex被设计为仅支持Windows 7启动的系统。因此它SRWLOCK直接使用。
的大小std::shared_mutex就是 的大小SRWLOCK。SRWLOCK与指针具有相同的大小(尽管在内部它不是指针)。
它仍然涉及一些可以避免的开销:它调用C++运行时库,只是为了调用Windows API,而不是直接调用Windows API。不过,下一个 ABI 似乎可以解决这个问题。
std::shared_mutex构造函数可以是 constexpr,因为SRWLOCK不需要动态初始化程序,但标准禁止自愿添加constexpr到标准类中。
| 归档时间: |
|
| 查看次数: |
2354 次 |
| 最近记录: |