【问题标题】:Keeping track of (stack-allocated) objects跟踪(堆栈分配的)对象
【发布时间】:2016-12-08 08:49:29
【问题描述】:

在一个相当大的应用程序中,我想跟踪有关某个类的对象的一些统计信息。为了不降低性能,我希望在拉取配置中更新统计信息。因此,我需要在某个位置引用每个活动对象。是否有一种惯用的方式来:

  1. 创建、搜索、迭代此类引用
  2. 自动管理(即在销毁时删除引用)

我在这里考虑的是一组智能指针,但是内存管理会有些颠倒:我不想在智能指针被销毁时销毁对象,而是希望智能指针被删除,当对象被销毁。理想情况下,我不想重新发明轮子。

我可以忍受删除指针的延迟,我只需要一种方法来快速使它们无效。

edit:因为稻谷要求它:基于拉取的收集的原因是获取信息可能相对昂贵。推送显然是一个干净的解决方案,但被认为过于昂贵。

【问题讨论】:

  • 你需要什么样的统计数据?如果它只是跟踪活动实例的计数,那么考虑为每个类存储一个原子整数,并可能制作一个简单的 RAII 包装器来进行计数(这意味着您只需将其放入类定义中即可完成工作)。
  • 如果有任何用处,here's a simple example 我在说什么。它不允许您迭代或搜索您的对象,但不清楚您为什么要这样做。以这种方式访问​​另一个堆栈可能不是一个好主意。这个例子只是提供了具有非常低开销和线程安全的对象计数。
  • 这基本上是一个推送更新,但被认为太昂贵了。
  • 更新单个原子将比跟踪所有内存分配和按需提供线程安全统计信息要便宜得多。
  • 嗯,看,我不知道。你实际上并没有告诉我们你想要什么统计数据,所以我不得不假设你想要数数。我确实问过,你没有发表评论,所以我认为我已经正确推断出你的意图。除非您提供真实信息,否则我更倾向于将此视为 XY 问题。

标签: c++ pointers reference global


【解决方案1】:

该语言没有允许您执行此操作的特殊功能。有时对象跟踪是通过滚动您自己的内存分配器来处理的,但这在堆栈上并不容易。

但如果您只使用堆栈,它实际上会使您的问题更容易,假设被跟踪的对象位于单个线程上。 C++ 对堆栈的构造和销毁顺序做出了特殊保证。即销毁顺序正好与构造顺序相反。

因此,您可以利用它在每个对象中存储一个指针,外加一个静态指针来跟踪最近的一个。现在您有了一个表示为链表的对象堆栈。

template <typename T>
class Trackable
{
public:
    Trackable()
    : previous( current() )
    {
        current() = this;
    }

    ~Trackable()
    {
        current() = previous;
    }

    // External interface
    static const T *head() const { return dynamic_cast<const T*>( current() ); }
    const T *next() const { return dynamic_cast<const T*>( previous ); }

private:
    static Trackable * & current()
    {
        static Trackable *ptr = nullptr;
        return ptr;
    }

    Trackable *previous;
}

例子:

struct Foo : Trackable<Foo> {};
struct Bar : Trackable<Bar> {};

//  :::

// Walk linked list of Foo objects currently on stack.
for( Foo *foo = Foo::head(); foo; foo = foo->next() )
{
    // Do kung foo
}

现在,诚然,这是一个非常简单的解决方案。在大型应用程序中,您可能有多个堆栈使用您的对象。您可以通过使 current() 使用 thread_local 语义来处理多个线程上的堆栈。虽然你需要一些魔法来完成这项工作,因为head() 需要指向线程注册表,这需要同步。

您绝对希望将所有堆栈同步到一个列表中,因为这会破坏程序的性能可伸缩性。

至于您的拉动要求,我认为这是一个单独的线程想要遍历列表。您需要一种同步方法,以便在迭代列表时阻止所有新对象的构造或破坏在 Trackable&lt;T&gt; 内。或类似的。

但至少您可以采用这个基本概念并将其扩展到您的需要。

请记住,如果您动态分配对象,则不能使用这种简单的列表方法。为此,您需要一个双向列表。

【讨论】:

    【解决方案2】:

    最简单的方法是在每个对象中包含代码,以便它在实例化时注册自己并在销毁时将自己删除。使用 CRTP 可以轻松注入此代码:

    template <class T>
    struct AutoRef {
    
        static auto &all() {
            static std::set<T*> theSet;
            return theSet;
        }
    
    private:
        friend T;
    
        AutoRef() { all().insert(static_cast<T*>(this)); }
        ~AutoRef() { all().erase(static_cast<T*>(this)); }
    };
    

    现在Foo 类可以从AutoRef&lt;Foo&gt; 继承,以便在AutoRef&lt;Foo&gt;::all() 中引用其实例。

    See it live on Coliru

    【讨论】:

    • 比我的解决方案简单得多 =) 虽然这确实具有对数复杂性。当他们抱怨每个对象有一个原子增量和减量“太昂贵”时,我认真对待他们。
    • 此外,我们的两个解决方案都遇到的一个问题是,即使您跨线程同步访问,您也不能保证仍在注册表中的对象当前没有被破坏并因此无效。修改这种行为并不难,但这是一个值得考虑的特殊细节。
    • @paddy OP 不想急切地推送统计信息,因为它们生成速度很慢并且在大多数情况下被忽略。没有提到计数或多线程。根据使用模式(插入/移除率、查询数量等),可以轻松地将容器换成另一个容器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-25
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-16
    • 2016-12-17
    相关资源
    最近更新 更多