内存管理,用于在C ++中的线程之间快速传递消息


9

假设有两个线程,它们通过彼此异步发送数据消息进行通信。每个线程都有某种消息队列。

我的问题很低:可以期望什么是管理内存的最有效方法?我可以想到几种解决方案:

  1. 发件人通过创建对象new。接听电话delete
  2. 内存池(用于将内存传输回发送方)
  3. 垃圾收集(例如Boehm GC)
  4. (如果对象足够小)按值复制以避免完全分配堆

1)是最明显的解决方案,因此我将其用于原型。很有可能它已经足够好了。但是与我的特定问题无关,我想知道如果要针对性能进行优化,哪种技术最有前途。

我希望从理论上讲池化是最好的,尤其是因为您可以使用有关线程之间信息流的更多知识。但是,我担心这也是最难解决的问题。很多调整... :-(

此后,垃圾收集应该很容易添加(在解决方案1之后),我希望它表现的很好。因此,我想如果1)效率太低,这是最实用的解决方案。

如果对象又小又简单,则按值复制可能是最快的。但是,我担心这会在受支持的消息的实施方面造成不必要的限制,因此我想避免这种情况。

Answers:


9

如果对象又小又简单,则按值复制可能是最快的。但是,我担心这会在受支持的消息的实施方面造成不必要的限制,因此我想避免这种情况。

如果您可以预见一个上限char buf[256],例如一种可行的替代方法,如果您不能在极少数情况下仅调用堆分配:

struct Message
{
    // Stores the message data.
    char buf[256];

    // Points to 'buf' if it fits, heap otherwise.
    char* data;
};

3

这将取决于您如何实现队列。

如果使用数组(循环式),则需要为解决方案4设置大小上限。如果使用链接队列,则需要分配对象。

然后,只需替换新的并使用AllocMessage<T>和删除即可轻松完成资源池化freeMessage<T>。我的建议是限制可能的大小,T并在分配混凝土时四舍五入messages

直接垃圾收集可以工作,但是在需要收集大部分时可能会导致长时间的停顿,并且(我认为)的性能会比新的/删除的要差一些。


3

如果使用C ++,则只需使用智能指针之一-unique_ptr会为您很好地工作,因为只有在没有人使用它的句柄时,它才会删除基础对象。您可以按值将ptr对象传递给接收者,而不必担心哪个线程应该删除它(在接收者未接收到该对象的情况下)。

您仍然需要处理线程之间的锁定,但是由于不会复制任何内存(只有ptr对象本身很小),因此性能会很好。

在堆上分配内存并不是有史以来最快的事情,因此使用池可以使速度更快。您只需从池中预先设置大小的堆中获取下一个块,因此只需使用现有库即可


2
锁定通常比内存复制要大得多。只是说。
tdammers 2012年

当你写的时候unique_ptr,我想你是认真的shared_ptr。但是,毫无疑问,使用智能指针可以很好地进行资源管理,但这并不能改变您使用某种形式的内存分配和释放的事实。我认为这个问题是更底层的。
5gon12eder'1

3

将对象从一个线程传递到另一个线程时,最大的性能损失是获取锁的开销。这大约是几微秒,大大超过一对new/所delete花费的平均时间(大约一百纳秒)。Sane的new实现方式试图避免锁定几乎所有成本,以免影响性能。

就是说,您要确保在将对象从一个线程传递到另一个线程时,不需要抓住锁。我知道实现此目的的两种通用方法。两者仅在一个发送者和一个接收者之间单向工作:

  1. 使用环形缓冲区。两个进程都控制一个指针进入该缓冲区,一个是读指针,另一个是写指针。

    • 发送方首先通过比较指针来检查是否有添加元素的空间,然后添加元素,然后递增写指针。

    • 接收器通过比较指针来检查是否有要读取的元素,然后读取该元素,然后递增读取的指针。

    指针必须是原子的,因为它们在线程之间共享。但是,每个指针仅由一个线程修改,另一个仅需要对该指针的读取访问。缓冲区中的元素可能是指针本身,这使您可以轻松地将环形缓冲区的大小调整为不会导致发送方块的大小。

  2. 使用始终至少包含一个元素的链表。接收者的指针指向第一个元素,发送者的指针指向最后一个元素。这些指针不共享。

    • 发送者为链接列表创建一个新节点,将其next指针设置为nullptr。然后,它更新next最后一个元素的指针以指向新元素。最后,它将新元素存储在其自己的指针中。

    • 接收器监视next第一个元素的指针,以查看是否有新数据可用。如果是这样,它将删除旧的第一个元素,前进其自身的指针以指向当前元素并开始处理它。

    在此设置中,next指针必须是原子的,并且发送者必须确保在设置了其next指针之后不要取消引用倒数第二个元素。这样做的好处是,发件人永远不必阻塞。

两种方法都比任何基于锁的方法都快得多,但是它们需要仔细实施才能正确。而且,当然,它们需要指针写入/加载的本机硬件原子性。如果您的atomic<>实现内部使用锁,那么您注定要失败。

同样,如果您有多个读者和/或作家,那么您注定要失败:您可能会想出一种无锁方案,但充其量实现起来将很棘手。这些情况很容易用锁处理。然而,一旦你抓住一个锁,你可以不用担心new/ delete性能。


+1我必须签出此环形缓冲区解决方案,以替代使用CAS循环的并发队列。听起来很有希望。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.