c – boost :: thread数据结构大小在荒谬的一面?
作者:互联网
编译器:linux上的clang x86-64.
已经有一段时间了,因为我编写了任何复杂的低级系统代码,并且我对系统原语(windows和pthreads / posix)进行了编程.所以,#s和out的内容已经从我的记忆中消失了.我目前正在使用boost :: asio和boost :: thread.
为了模拟异步函数执行器的同步RPC(具有多个线程的boost :: io_service io :: service :: run’ing,其中请求是io_serviced :: post’ed),我使用的是boost同步原语.为了好奇,我决定改变基元的大小.这就是我所看到的.
struct notification_object
{
bool ready;
boost::mutex m;
boost::condition_variable v;
};
...
std::cout << sizeof(bool) << std::endl;
std::cout << sizeof(boost::mutex) << std::endl;
std::cout << sizeof(boost::condition_variable) << std::endl;
std::cout << sizeof(notification_object) << std::endl;
...
输出:
1
40
88
136
互斥锁的四十个字节?? ?? ? WTF! 88为条件_变量!!!请记住,我被这个膨胀的大小击退,因为我正在考虑一个可以创建数百个notification_object的应用程序
这种便携性开销似乎很荒谬,有人可以证明这一点吗?据我所知,这些原语应该是4或8字节宽,具体取决于CPU的内存模型.
解决方法:
当您查看任何类型的同步原语的“大小开销”时,请记住,这些不能太紧密地打包.这是因为例如如果共享高速缓存行的两个互斥锁最终会被缓存废弃(假共享),如果它们同时使用,即使获取这些锁的用户永远不会“冲突”.即想象两个线程运行两个循环:
for (;;) {
lock(lockA);
unlock(lockA);
}
和
for (;;) {
lock(lockB);
unlock(lockB);
}
与运行一个循环的一个线程相比,当在两个不同的线程上运行时,您将看到两次迭代次数,当且仅当两个锁不在同一个高速缓存行中时.如果lockA和lockB在同一个高速缓存行中,则每个线程的迭代次数将减半 – 因为具有这两个锁的高速缓存行将在执行这两个线程的cpu核之间永久地反弹.
因此,即使自旋锁或互斥锁下面的原始数据类型的实际数据大小可能只是一个字节或32位字,这种对象的有效数据大小通常也更大.
断言“我的互斥体太大”之前请记住这一点.实际上,在x86 / x64上,40字节太小而不能防止错误共享,因为当前的缓存行至少有64个字节.
除此之外,如果您高度关注内存使用情况,请考虑通知对象不必是唯一的 – 条件变量可以用于触发不同的事件(通过boost :: condition_variable知道的谓词).因此,可以对整个状态机使用单个互斥/ CV对,而不是每个状态使用一个这样的对.同样适用于例如线程池同步 – 拥有比线程更多的锁定并不一定有益.
编辑:有关“虚假共享”的更多参考(以及在同一个高速缓存行中托管多个原子更新的变量所造成的负面性能影响),请参阅(以及其他)以下SO帖子:
> false sharing in boost::detail::spinlock_pool?
> False sharing and pthreads
> False Sharing and Atomic Variables
如上所述,当在多核,每个核心的高速缓存配置中使用多个“同步对象”(无论是原子更新的变量,锁,信号量……)时,允许它们中的每一个都有一个单独的高速缓存行空间.你在这里交换内存使用的可扩展性,但实际上,如果你进入你的软件需要几百万个锁的区域(制作那些GB的内存),你要么拥有几百GB内存的资金(和一百个CPU核心),或者你在软件设计中做错了什么.
在大多数情况下(对于类/结构的特定实例的锁/原子),只要包含原子变量的对象实例足够大,就可以免费获得“填充”.
标签:systems-programming,boost-thread,c,boost-asio,micro-optimization 来源: https://codeday.me/bug/20191006/1863016.html