在荒谬的一面boost :: thread数据结构大小?

Has*_*yed 3 c++ boost-thread micro-optimization boost-asio systems-programming

编译器:linux上的clang ++ x86-64.

已经有一段时间了,因为我编写了任何复杂的低级系统代码,并且我对系统原语(windows和pthreads/posix)进行了编程.所以,#s和out的内容已经从我的记忆中消失了.我正在boost::asioboost::thread目前一起工作.

为了模拟针对异步函数执行器的同步RPC(在请求被编辑的地方boost::io_service有多个线程),我正在使用boost同步原语.为了好奇,我决定使用这些原语.这就是我所看到的.io::service::runio_serviced::postsizeof

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;
...
Run Code Online (Sandbox Code Playgroud)

输出:

1
40
88
136
Run Code Online (Sandbox Code Playgroud)

互斥锁的四十个字节?? ?? ?WTF!88为条件_变量!!! 请记住,我被这个体积臃肿击退,因为我想,可能造成上百的应用程序notification_object

这种便携性开销似乎很荒谬,有人可以证明这一点吗?据我所知,这些原语应该是4或8字节宽,具体取决于CPU的内存模型.

Fra*_*kH. 23

当您查看任何类型的同步原语的"大小开销"时,请记住,这些不能太紧密地打包.这是因为例如,如果共享高速缓存行的两个互斥体最终会在缓存中丢弃(错误共享),如果它们同时在使用中,即使获取这些锁的用户永远不会"冲突".即想象两个线程运行两个循环:

for (;;) {
    lock(lockA);
    unlock(lockA);
}
Run Code Online (Sandbox Code Playgroud)

for (;;) {
    lock(lockB);
    unlock(lockB);
}
Run Code Online (Sandbox Code Playgroud)

与运行一个循环的一个线程相比,当在两个不同的线程上运行时,您将看到两次迭代次数,当且仅当两个锁不在同一个高速缓存行中时.如果lockA并且lockB在同一个高速缓存行中,每个线程的迭代次数将减半 - 因为具有这两个锁的高速缓存行将在执行这两个线程的cpu核之间永久地反弹.

因此,即使自旋锁或互斥锁下的原始数据类型的实际数据大小可能只是一个字节或32位字,这种对象的有效数据大小通常也更大.

断言"我的互斥体太大"之前请记住这一点.事实上,在x86/x64,40个字节是太小,以防止假共享,超高速缓存行目前有至少64个字节.

除此之外,如果您高度关注内存使用情况,请考虑通知对象不必是唯一的 - 条件变量可以用于触发不同的事件(通过predicateboost::condition_variable知道).因此,可以对整个状态机使用单个互斥/ CV对,而不是每个状态使用一个这样的对.同样适用于例如线程池同步 - 拥有比线程更多的锁定并不一定有益.

编辑:有关"虚假共享"的更多参考(以及在同一个高速缓存行中托管多个原子更新的变量所造成的负面性能影响),请参阅(以及其他)以下SO帖子:

如上所述,当在多核,每个核心的高速缓存配置中使用多个"同步对象"(无论是原子更新的变量,锁,信号量......)时,允许它们中的每一个都有一个单独的高速缓存行空间.你在这里交换内存使用的可扩展性,但实际上,如果你进入你的软件需要几百万个锁的区域(制作那些GB的内存),你要么拥有几百GB内存的资金(和一百个CPU核心),或者你在软件设计中做错了什么.

在大多数情况下(锁定/原子的特定实例class/ struct),你得到的"填充"为自由,只要包含原子变量足够大的对象实例.


NPE*_*NPE 19

在我的64位Ubuntu框中,以下内容:

#include <pthread.h>
#include <stdio.h>

int main() {
  printf("sizeof(pthread_mutex_t)=%ld\n", sizeof(pthread_mutex_t));
  printf("sizeof(pthread_cond_t)=%ld\n", sizeof(pthread_cond_t));
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

版画

sizeof(pthread_mutex_t)=40
sizeof(pthread_cond_t)=48
Run Code Online (Sandbox Code Playgroud)

这表明你的主张

这种便携性开销似乎很荒谬,有人可以向我证明这一点吗?据我所知,这些原语应该是4或8字节宽,具体取决于CPU的内存模型.

根本不是真的.

如果您想知道额外的40个字节boost::condition_variable来自何处,Boost类使用内部互斥锁.

简而言之,与此平台相比,此平台的开销boost::mutex几乎为零pthread_mutex_t,并且boost::condition_variable具有额外内部互斥锁的开销.后者是否适合您的申请由您决定.

PS我会鼓励你坚持事实,避免在你的帖子中使用煽动性语言.我一个人几乎决定完全忽视你的帖子,因为它的语气.

  • 在Windows上,您在用户代码中看到的变量很可能是在某处引用更大数据结构的句柄 - 请确保将其考虑在内. (2认同)

orl*_*rlp 6

看看实施情况:

class mutex : private noncopyable
{
public:
    friend class detail::thread::lock_ops<mutex>;

    typedef detail::thread::scoped_lock<mutex> scoped_lock;

    mutex();
    ~mutex();

private:
#if defined(BOOST_HAS_WINTHREADS)
    typedef void* cv_state;
#elif defined(BOOST_HAS_PTHREADS)
    struct cv_state
    {
        pthread_mutex_t* pmutex;
    };
#elif defined(BOOST_HAS_MPTASKS)
    struct cv_state
    {
    };
#endif
    void do_lock();
    void do_unlock();
    void do_lock(cv_state& state);
    void do_unlock(cv_state& state);

#if defined(BOOST_HAS_WINTHREADS)
    void* m_mutex;
#elif defined(BOOST_HAS_PTHREADS)
    pthread_mutex_t m_mutex;
#elif defined(BOOST_HAS_MPTASKS)
    threads::mac::detail::scoped_critical_region m_mutex;
    threads::mac::detail::scoped_critical_region m_mutex_mutex;
#endif
};
Run Code Online (Sandbox Code Playgroud)

现在,让我去除非数据部分并重新排序:

class mutex : private noncopyable {
private:
#if defined(BOOST_HAS_WINTHREADS)
    void* m_mutex;
#elif defined(BOOST_HAS_PTHREADS)
    pthread_mutex_t m_mutex;
#elif defined(BOOST_HAS_MPTASKS)
    threads::mac::detail::scoped_critical_region m_mutex;
    threads::mac::detail::scoped_critical_region m_mutex_mutex;
#endif
};
Run Code Online (Sandbox Code Playgroud)

因此,除了noncopyable我看到系统互斥体不会发生的开销不大.

  • 并且noncopyable可用于空基类优化. (2认同)