5

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

自从我编写任何复杂的低级系统代码以来已经有一段时间了,我通常针对系统原语(windows 和 pthreads/posix)进行编程。所以,in#s和out的已经从我的记忆中溜走了。我正在boost::asio和现在一起工作boost::thread

为了模拟针对异步函数执行器的同步 RPC(在请求被 ed 的地方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;
...

输出:

1
40
88
136

一个互斥体 40 个字节???? ? 哇!条件变量为 88 !!! 请记住,我对这种臃肿的尺寸感到厌恶,因为我正在考虑一个可以创建数百个notification_object's的应用程序

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

4

4 回答 4

25

当您查看任何类型的同步原语的“大小开销”时,请记住不能将它们打包得太紧密。之所以如此,是因为例如,如果两个互斥锁同时使用,则共享一个缓存行最终会导致缓存垃圾(错误共享),即使获取这些锁的用户从不“冲突”。即想象两个线程运行两个循环:

for (;;) {
    lock(lockA);
    unlock(lockA);
}

for (;;) {
    lock(lockB);
    unlock(lockB);
}

当且仅当两个锁不在同一个缓存行中时,与一个线程运行一个循环相比,在两个不同线程上运行时,您将看到两倍的迭代次数。如果lockAlockB位于同一缓存线中,则每个线程的迭代次数将减半 - 因为具有这两个锁的缓存线将在执行这两个线程的 cpu 内核之间永久反弹。

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

在断言“我的互斥体太大”之前请记住这一点。事实上,在 x86/x64 上,40 Bytes太小,无法防止虚假共享,因为 cachelines 目前至少有 64 Bytes。

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

编辑:有关“错误共享”的更多参考资料(以及在同一缓存行中托管多个原子更新的变量所造成的负面性能影响),请参阅(除其他外)以下 SO 帖子:

如前所述,当在多核、每核缓存配置中使用多个“同步对象”(无论是原子更新的变量、锁、信号量……)时,允许它们中的每一个单独的缓存线空间。你在这里用内存来换取可扩展性,但实际上,如果你进入了你的软件需要数百万个锁(即 GB 内存)的区域,你要么有几百 GB 内存的资金(以及一百个 CPU 内核),或者您在软件设计中做错了什么。

在大多数情况下(a 的特定实例的锁/原子classstruct,只要包含原子变量的对象实例足够大,您就可以免费获得“填充”。

于 2011-07-25T16:22:24.307 回答
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;
}

印刷

sizeof(pthread_mutex_t)=40
sizeof(pthread_cond_t)=48

这表明您声称

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

根本不是真的。

如果您想知道额外的 40 个字节是boost::condition_variable从哪里来的,Boost 类使用了一个内部互斥体。

简而言之,在这个平台上,与相比,开销boost::mutex正好为零pthread_mutex_t,并且boost::condition_variable具有额外的内部互斥体的开销。后者是否适合您的申请由您决定。

PS 我会鼓励你坚持事实,避免在你的帖子中使用煽动性语言。我几乎决定忽略你的帖子,纯粹是因为它的语气。

于 2011-07-25T13:07:14.177 回答
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
};

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

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
};

所以除了noncopyable我看不到系统互斥体不会发生的太多开销。

于 2011-07-25T13:10:27.307 回答
3

抱歉,我在这里发表评论,但我没有足够的声誉来添加评论。

@FrankH,缓存垃圾不是使数据结构更大的好理由。有些缓存行甚至可以有 128 字节的大小,但这并不意味着互斥锁必须这么大。

我认为必须警告程序员将内存中的同步对象分开,这样它们就不会共享相同的缓存行。通过将对象插入足够大的数据结构中可以实现什么,而不会使数据结构因未使用的字节而膨胀。另一方面,插入未使用的字节会降低程序速度,因为 CPU 必须获取更多的高速缓存行才能访问相同的结构。

@Hassan Syed,我不认为互斥锁是在这种类型的缓存优化中被编程思考的。相反,我认为这是它们为支持优先级继承、嵌套锁等思想而编程的方式。作为建议,如果您的程序中需要大量互斥锁,请考虑使用诸如互斥锁池(数组)之类的东西,并在节点中仅存储一个索引(当然要注意内存分离)。我让你思考这个解决方案的细节。

于 2014-04-02T18:39:22.880 回答