如何实现高效的C++运行时统计信息

How to implement efficient C++ runtime statistics

本文关键字:C++ 运行时 统计 信息 高效 何实现 实现      更新时间:2023-10-16

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

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

还可以以更通用的方式进行其他统计信息,例如每分钟生成多少线程,调用多少new/delete,或者应用程序的更具体方面;您命名。

很棒的是类似于谷歌浏览器的"内部页面",如 网络 or chrome://tracing ,但采用命令行方式。

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

你们对这件事有什么指示吗?

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

我建议在你的代码中,你维护递增的计数器。计数器可以是类成员或全局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

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

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

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

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

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

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

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

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

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

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

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

在 Web 上搜索"调试日志记录"。 应该找到一些可以用来玩的来源。 我去过的大多数商店通常都会自己卷。

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

最坏的情况是将数据写出到调试(串行)端口。

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

看看 valgrind/callgrind。

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

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

因此,我切换到了一个略有不同的统计信息报告模型 - 我现在没有主动从主线程查询工作线程,而是让工作线程将其基本统计信息计数器报告到其独占统计信息存储库,主线程可以随时查询该存储库,而不会对工作线程产生直接影响。

如果你在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;
};