【问题标题】:How to implement efficient C++ runtime statistics如何实现高效的 C++ 运行时统计
【发布时间】:2012-07-06 15:39:18
【问题描述】:

我想知道是否有监控我的应用程序内部的好方法,最好是以现有库的形式。

我的应用程序是高度多线程的,并使用消息系统在线程之间和与外部世界进行通信。我的目标是监控发送的消息类型、频率等。

还可能有其他更通用的统计信息,例如每分钟产生多少线程,调用了多少新/删除,或更具体的应用程序方面;你的名字。

最棒的是您为 Google Chrome 提供的“内部页面”,例如 net 或 chrome://tracing ,但以命令行方式。

如果有一个足够通用的库来适应我的应用程序的特殊性,那就太好了。
否则我准备实现一个可以完成这项工作的小类,但我不知道从哪里开始。我认为最重要的是代码不能过多干扰,以免影响性能。

你们对这个问题有什么建议吗?

编辑:我的应用程序在 Linux 上运行,在嵌入式环境中,遗憾的是 Valgrind 不支持 :(

【问题讨论】:

  • 会支持 gprof 吗? -pg 在 gcc 编译器上?
  • 是的,这是我们拥有的一件事。虽然我的问题是针对一个运行了很长时间的程序(服务),所以应该可以在运行时访问统计信息:-)
  • 很遗憾,您无法添加另一个标签“嵌入”。
  • 已添加嵌入式 :) 好点。

标签: c++ linux statistics embedded runtime


【解决方案1】:

我建议您在代码中维护递增的计数器。计数器可以是static 类成员或全局变量。如果您使用一个类来定义您的计数器,您可以让构造函数将您的计数器注册到单个存储库以及名称。然后,您可以通过查询存储库来查询和重置您的计数器。

struct Counter {
    unsigned long c_;
    unsigned long operator++ () { return ++c_; }
    operator unsigned long () const { return c_; }
    void reset () { unsigned long c = c_; ATOMIC_DECREMENT(c_, c); }
    Counter (std::string name);
};

struct CounterAtomic : public Counter {
    unsigned long operator++ () { return ATOMIC_INCREMENT(c_, 1); }
    CounterAtomic (std::string name) : Counter(name) {}
};

ATOMIC_INCREMENT 将是一种特定于平台的机制,用于以原子方式递增计数器。为此,GCC 提供了一个内置的__sync_add_and_fetchATOMIC_DECREMENT 类似,GCC 内置 __sync_sub_and_fetch

struct CounterRepository {
    typedef std::map<std::string, Counter *> MapType;
    mutable Mutex lock_;
    MapType map_;
    void add (std::string n, Counter &c) {
        ScopedLock<Mutex> sl(lock_);
        if (map_.find(n) != map_.end()) throw n;
        map_[n] = &c;
    }
    Counter & get (std::string n) const {
        ScopedLock<Mutex> sl(lock_);
        MapType::const_iterator i = map_.find(n);
        if (i == map_.end()) throw n;
        return *(i->second);
    }
};

CounterRepository counterRepository;

Counter::Counter (std::string name) {
    counterRepository.add(name, *this);
}

如果您知道同一个计数器将增加多个线程,请使用CounterAtomic。对于特定于线程的计数器,只需使用Counter

【讨论】:

  • 这是一个好的开始 IMO,比 valgrind 的建议要好,因为它会违反 ..so that performances are not impacted.. 的要求。这不解决的是多线程方面,递增/重置计数器不一定是原子的......我会考虑在线程私有变量中维护其中一些......
  • @nhed:感谢您提醒我关于 OP 的 MT 要求。我用原子操作更新了答案。
  • 这是一个很棒的答案。我会试试那个东西,看看效果如何。也感谢__sync_*_and_fetch,我不知道,我使用了一些互斥锁!
  • @Gui13:谢谢。请注意,我刚刚修复了 reset 中的一个错误。
【解决方案2】:

我推测您正在尝试收集运行时统计信息——例如您发送了多少字节、您运行了多长时间以及用户激活了特定功能的次数。

通常,为了从各种来源(如工作线程)编译诸如此类的运行时统计信息,我会让每个来源(线程)增加自己的最基本数据的本地计数器,但不执行任何尚未对该数据进行冗长的数学或分析。

然后回到主线程(或您希望分析和显示这些统计数据的任何地方),我向每个工作线程发送RequestProgress 类型的消息。作为响应,工作线程将收集所有基本数据,并可能执行一些简单的分析。这些数据连同基本分析的结果一起在ProgressReport 消息中发送回请求(主)线程。然后主线程聚合所有这些数据,进行额外的(可能是昂贵的)分析、格式化和显示给用户或记录。

主线程根据用户请求(例如当他们按下S 键时)或按时间间隔发送此RequestProgress 消息。如果我想要一个定时间隔,我通常会实现另一个新的“心跳”线程。这个线程所做的只是Sleep() 指定时间,然后向主线程发送Heartbeat 消息。主线程反过来通过向每个要从中收集统计信息的工作线程发送RequestProgress 消息来处理此Heartbeat 消息。

收集统计数据的行为似乎应该相当简单。那么为什么会有如此复杂的机制呢?答案有两个。

首先,工作线程有工作要做,而不是计算使用统计信息。试图重构这些线程以承担与其主要目的正交的第二个责任有点像试图将一个方形钉塞进一个圆孔。它们不是为此而构建的,因此代码会被拒绝编写。

其次,如果您尝试做太多、太频繁,运行时统计信息的计算可能会很昂贵。例如,假设您有一个在网络上发送多播数据的工作线程,并且您想要收集吞吐量数据。多少字节,在多长时间内,平均每秒多少字节。您可以让工作线程自行计算所有这些,但工作量很大,而且 CPU 时间最好由工作线程来做它应该做的事情——发送多播数据。相反,如果您只是为每次发送消息时发送的字节数增加一个计数器,则计数对线程性能的影响很小。然后响应偶尔的RequestProgress 消息,您可以计算出开始和停止时间,并将其发送给主线程,让主线程完成所有的划分等。

【讨论】:

    【解决方案3】:

    使用共享内存(POSIX、System V、mmap 或任何可用的内存)。通过将原始内存块转换为数组定义,在其中放置一个固定长度的 volatile 无符号 32 位或 64 位整数数组(即您可以在平台上以原子方式递增的最大值)。请注意, volatile 不会让您获得原子性。它可以防止可能会破坏您的统计值的编译器优化。使用 gcc 的 __sync_add_and_fetch() 或较新的 C++11 atomic 类型等内在函数。

    然后您可以编写一个附加到同一块共享内存的小程序,并可以打印出一个或所有统计信息。这个小型统计阅读器程序和您的主程序必须共享一个公共头文件,该文件强制执行每个统计数据在数组中的位置。

    这里的明显缺点是您被固定数量的计数器所困。但就性能而言,它很难被击败。影响是程序中各个点处整数的原子增量。

    【讨论】:

      【解决方案4】:

      在嵌入式系统中,一种常见的技术是为“日志”保留一块内存,并将其视为循环队列。编写一些可以读取这块内存的代码;这将有助于在运行时拍摄“快照”。

      在网上搜索“调试日志”。应该打开一些你可以用来玩的来源。我去过的大多数商店通常都是自己开的。

      如果您有额外的非易失性内存,您可以保留一个区域并写入该区域。如果您的系统足够大以支持文件系统,这也将包括文件。

      最坏的情况,将数据写入调试(串行)端口。

      对于实际的实时测量,我们通常使用连接到 GPIO 或测试点的示波器并将脉冲输出到 GPIO/测试点。

      【讨论】:

        【解决方案5】:

        看看 valgrind/callgrind。

        它可以用于分析,这就是我理解你正在寻找的。我认为它在运行时不起作用,但它可以在您的流程完成后生成。

        【讨论】:

        • @Gui13:您是否正在寻找编译时统计数据,就像 valgrind 可能提供的那样?或者您是否在寻找运行时统计信息,例如用户点击弹出式鸭子的次数?
        • 遗憾的是,我的应用程序在 valgrind 不支持的嵌入式平台上运行。
        【解决方案6】:

        这是一个很好的答案,@John Dibling!我有一个与此非常相似的系统。但是,我的“stat”线程每秒查询工作线程 10 次,它会影响工作线程的性能,因为每次“stat”线程请求数据时,都会有一个关键部分访问这些数据(计数器等)并且它表示在检索此数据时工作线程被阻塞。事实证明,在工作线程负载很重的情况下,这种 10Hz 的统计查询会影响工作线程的整体性能。

        所以我切换到一个稍微不同的统计报告模型——我现在没有从主线程主动查询工作线程,而是让工作线程将它们的基本统计计数器报告给他们的专有统计存储库,可以由主线程查询对工人没有直接影响的任何时间。

        【讨论】:

          【解决方案7】:

          如果你使用 C++11,你可以使用 std::atomic

          #include <atomic>
          
          class GlobalStatistics {
          public:
          
              static GlobalStatistics &get() {
                  static GlobalStatistics instance;
                  return instance;
              }
          
              void incrTotalBytesProcessed(unsigned int incrBy) {
                  totalBytesProcessed += incrBy;
              }
          
              long long getTotalBytesProcessed() const { return totalBytesProcessed; }
          
          
          private:
          
              std::atomic_llong totalBytesProcessed;
          
              GlobalStatistics() { }
              GlobalStatistics(const GlobalStatistics &) = delete;
              void operator=(const GlobalStatistics &) = delete;
          };
          

          【讨论】:

            猜你喜欢
            • 2018-12-25
            • 2020-09-08
            • 2012-11-23
            • 1970-01-01
            • 1970-01-01
            • 2021-09-02
            • 2013-09-18
            • 2016-10-11
            • 1970-01-01
            相关资源
            最近更新 更多