【问题标题】:Efficiently computing date/time stamp for logging purposes on unix/win32在 unix/win32 上有效地计算日期/时间戳以进行日志记录
【发布时间】:2012-08-03 02:06:47
【问题描述】:

我的情况是,在对我们的系统进行剖析和分析后,得出的结论是,系统的日志记录组件是占用总运行时间约 17% 的众多瓶颈之一 - 很多事情被记录下来了。

其中,记录器消耗的大约 5% 的时间与生成以下格式的 ascii 日期/时间戳有关:YYYYMMDD HHMMSS.fff - 我们大约每秒记录大约 700k 行。 (每秒大约 700K x (localtime 和 gettimeofday) 次调用)

我想知道 SOers 有哪些技术可以有效地生成时间戳。

欢迎跨平台解决方案。

注意 1:我们研究了 Boost.datetime - 它很棒,但对我们的需求来说有点太慢了,std::chrono 是一个完美的解决方案,但不幸的是我们必须支持 pre c++11 编译器。

注意 2:我们已经实现了一个简单的优化,它每 24 小时只计算一个日期部分 (yyyymmdd),因此每行只有 1 个 gettimeofday 调用 - 但没有多大帮助。

【问题讨论】:

  • formatting 是单独占 5%,还是包括其他获取时间的调用? (虽然,即使 5% 变成了 0%,它仍然是 ~16.7% 的总数:-)
  • @pst:这只是填充各种时间结构的调用。格式(转换为 ascii)完全是另一个问题。
  • 您愿意对日志进行后处理吗?
  • @PuraVida:不-我知道你要去哪里,而是记录序列号,每 100 毫秒左右记录一个时间戳,然后平均推断时间间隔之间的时间和这些时间之间的序列号。这样做的问题是,事情可能会在时间桶开始时发生/聚集,然后在它之后没有其他任何事情。在现实生活中效果不佳。
  • 您是否缓存了格式化的结果? (即如果毫秒没有改变,那么使用之前的结果)。我还要问一个显而易见的问题 - 你真的需要每秒记录 70 万次吗?

标签: c++ winapi unix logging timestamp


【解决方案1】:

如果您可以选择使用 C++11,请查看std::chrono

否则,优化将取决于您需要的分辨率。 我会问您是否绝对需要日志记录时间戳,或者带有序列信息的偶尔时间戳是否有用?

例子:

<timestamp1> <seq_num_0> ...
<timestamp1> <seq_num_1> ...
....
<timestamp1> <seq_num_n-1> ...
<timestamp2> <seq_num_0> ...

在我看来,你有两个问题:

  1. 与其他系统同步时间戳
  2. 在单个系统上获取准确的时间戳

我会使用基于计时器的系统每毫秒更新时间戳两次,并在更新之间重复使用它。然后,我会确保运行您的代码的系统的时钟与原子钟同步。您生成两次时间戳以尝试补偿底层操作系统计时器机制的脆弱性。

我不认为你能比这更好。

编辑:实际上,你可以。确保仅在时间戳字符串更改时对其进行格式化。你也不需要序列号,如果你能保证条目按照它们进入的顺序被记录。鉴于这两个假设,你的日志记录问题现在减少到你可以连接和写出两个字符串的速度。

更新 2:如果 BOOST 不适合并且您不能使用 C++11,则归结为:

  1. 使用计时器每毫秒设置两次时间戳并设置其格式 - 您可以通过操作系统级别的 API 执行此操作。
  2. 确保事件按照它们进入的顺序记录。

假设 I/O 不是您的瓶颈,那么您的问题就是快速字符串连接之一。

【讨论】:

  • 时间戳至关重要,因为我们使用它们来匹配其他系统上发生的事件。
【解决方案2】:

我会延迟所有格式,直到真正需要:

struct log_entry {
    struct timeval timestamp;
    unsigned int code;
    union {
        struct param1 p1;
        struct param2 p2;
    };
};

paramN 结构包含适用于事件的数据,无论它们当时处于何种形式,但作为副本(因此可以单独分析日志数据)。

根据您的要求,您可以将此数据保存在环形缓冲区中,并不断覆盖旧数据,或者在达到一定百分比时将其转储到磁盘。

【讨论】:

  • +1 - 考虑到 OP 对记录率的苛刻要求,这基本上就是我要做的。创建一个由 1M 组成的数组作为循环缓冲区,然后每隔 100 毫秒,向记录器线程发出开始/结束索引,如果时间戳成员仅包含滴答计数,则可能还有挂壁时间,(或任何操作系统上最快的时间相关 API 返回的任何内容)。记录器线程可以进行格式化、向墙上时间添加偏移量、以漂亮的大块进行插值等。
【解决方案3】:

编辑:现在有多个反对者。请发表评论,以便我可以正确解决问题。谢谢!

您可以重新组织您的代码,以便您的记录器从其他线程每秒更新 N 次(取决于您所需的分辨率)的缓冲区中读取日期时间戳字符串。每秒 4 次:

struct current_time_stamp {
    char timestr_[4][16];
    unsigned index_;
    unsigned subsecond_;
    const char *get () const { return timestr_[index_%4]; }
    void update () {
        // ... update string in timestr_[(index_+1)%4] ...
        // ... if (index_ + 1)%4 is zero, recompute subsecond_
        ATOMIC_INCREMENT(index_);
        // ... also need a memory barrier for timestr_ update
    }
};

每个日志的亚秒级分辨率将从高性能计数器读取。 DeadMG 建议在 Windows 上使用 QueryPerformanceTimer,在 Linux(和 POSIX)上建议使用 clock_gettime。但是,如果这些实现的开销对您来说仍然很高,您可以使用内联汇编直接查询处理器上的时间戳计数器(对于 x86,请参阅rdtsc)。亚秒值与记录在结构中的值相差 delta 以获得正确的偏移量。

如果您可以避免以二进制格式记录时间戳,那将摆脱格式问题。

【讨论】:

  • 你为什么不简单地使用一个普通的高性能计数器,比如QueryPerformanceCounter?没有理由在这里使用内联汇编程序。
  • @DeadMG:问题是关于性能的。我没有在 Windows 上使用 QueryPerformanceCounter 的经验,但直接在 Linux 上使用 rdtsc 比直接调用 clock_gettime 高 100 倍。
  • 完全针对您的实现、编译器和您的情况。您需要个人资料数据来为 OP 证明这一点。
  • OP 说他每秒调用gettimeofday 700K 次。我的解决方案将其减少到比这小得多的数字,但仍然需要一种有效的方法来提取亚秒级分辨率。我提出了一个比gettimeofday 更快的建议。你觉得我的建议会慢吗?
  • @DeadMG:请让我知道您认为错误的答案还有什么。谢谢,问候
猜你喜欢
  • 1970-01-01
  • 2018-02-07
  • 2020-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-02
  • 2011-02-08
相关资源
最近更新 更多