2

我在多线程应用程序中有一类对象,其中每个线程都可以标记要删除的对象,然后中央垃圾收集器线程实际上会删除该对象。线程通过访问内部布尔值的成员方法进行通信:

class MyObjects {
...   
bool shouldBeDeleted() const
{
   return m_Delete;
}

void
markForDelete()
{
   m_Delete = true;
}
...
   std::atomic< bool >                                        m_IsObsolete;
}

由于 Thread Sanitizer 一直在抱怨,该 bool 过去已被其他人设为原子。但是,perf 现在表明在内部原子负载期间存在处理开销:

   │     ↓ cbz    x0, 3f4                                                                                                                                                                                                                                                                                                                                                                                            

   │     _ZNKSt13__atomic_baseIbE4loadESt12memory_order():                                                                                                                                                                                                                                                                                                                                                           

   │           {                                                                                                                                                                                                                                                                                                                                                                                                     

   │             memory_order __b = __m & __memory_order_mask;                                                                                                                                                                                                                                                                                                                                                       

   │             __glibcxx_assert(__b != memory_order_release);                                                                                                                                                                                                                                                                                                                                                      

   │             __glibcxx_assert(__b != memory_order_acq_rel);                                                                                                                                                                                                                                                                                                                                                      

   │                                                                                                                                                                                                                                                                                                                                                                                                                 

   │             return __atomic_load_n(&_M_i, __m);                                                                                                                                                                                                                                                                                                                                                                 

   │       add    x0, x0, #0x40                                                                                                                                                                                                                                                                                                                                                                                          

 86,96 │       ldarb  w0, [x0]  

目标平台是 GCC、Aarch64 和 Yocto Linux。

现在我的问题如下:

  • 在这种情况下真的需要原子吗?bool 的转换是一种方式(从 false 到 true),在对象存在时无法返回,因此不一致仅意味着对象稍后被删除,对吗?

  • 是否有替代方案可以std::atomic<bool>使 Thread Sanitizer 静音但在计算上比 Thread Sanitizer 便宜std::atomic<bool>

4

2 回答 2

3

一个明显的修改可能是指定memory_order_relaxed最小化内存障碍。

请参阅https://en.cppreference.com/w/cpp/atomic/memory_order

https://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/

另请参阅 Herb Sutter 的经典“原子武器”:https ://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

m_Delete.store (true, std::memory_order_relaxed);

警告请参阅上面的文章) - 如果对被标记为删除的对象有任何共同依赖关系(例如,另一个状态变量、释放资源等),那么您可能需要使用memory_order_release以确保can be deleted标志设置最后出现并且不被重新排序编译器优化器。

假设“垃圾收集器”检查can be deleted它不需要memory_order_acquire在负载中使用的标志;放松就足够了。否则,它将需要使用获取来保证在读取标志之前不会重新排序任何相互依赖的访问。

于 2020-10-16T14:09:25.577 回答
2

问题(正如 OP 在评论中阐明的那样)不是真正的 GC,而是延迟删除单独线程上的对象,以便从删除所需的时间减轻主处理线程的负担。所有要删除的对象都会在某个时候被标记 - 稍后删除线程会出现并删除它们。

首先考虑:为了满足程序的性能目标——特别是延迟,延迟删除真的是必要的吗?实际上影响延迟的可能只是额外的开销。(或者,可能还需要考虑不同的性能目标,例如吞吐量。)延迟删除在所有情况下都不是明显的性能优势 - 您需要确定它是否适合每种情况。(例如,甚至可能并非所有删除都需要:也许一些删除可以立即在线完成而不会影响性能,而另一些则需要延迟。这可能是因为,例如,不同的处理线程正在执行不同的操作不同的延迟/吞吐量要求。)

现在来一个解决方案:由于我们正在谈论延迟删除 - 删除线程没有理由需要扫描所有对象以寻找要删除的对象(每次它进行完整扫描时)。相反,在您将对象标记为删除时支付稍高的费用,并且无需支付任何费用来扫描所有对象。通过将已删除的对象链接到删除工作列表来执行此操作。那里有同步成本(除了明显的锁之外,还可以通过各种方式最小化),但是每个对象支付一次,而不是每次扫描每个对象支付一次。

(也不必是链表。如果在一段时间内可以删除多少个对象有上限,则可以使用适当的数组。)

通过将这个问题更准确地描述为“延迟删除”而不是“垃圾收集”,还有其他可能性:取消了一些约束(也许添加了其他约束)。

于 2020-10-16T14:53:41.590 回答