计算滚动窗口中每秒的消息数量?

时间:2010-02-16 00:32:29

标签: algorithm data-structures frame-rate

我的消息以毫秒级的分辨率进入我的程序(从零到几百条消息,毫秒)。

我想做一些分析。具体来说,我想维护消息计数的多个滚动窗口,在消息进入时更新。例如,

  • 最后一秒的消息数
  • 最后一分钟的消息数
  • 过去半小时中的消息数除以过去一小时内的消息数

我不能只保留一个简单的计数,例如“最后一秒的1,017条消息”,因为我不知道消息何时超过1秒,因此不再应该在计数...

我想到维护所有消息的队列,搜索超过一秒的最年轻的消息,并从索引中推断出计数。然而,这似乎太慢了,会耗费大量的记忆。

如何在程序中跟踪这些计数,以便我可以实时有效地获取这些值?

5 个答案:

答案 0 :(得分:14)

循环缓冲区最容易处理。

循环缓冲区具有固定数量的元素和指向它的指针。您可以向缓冲区添加元素,并在执行时将指针递增到下一个元素。如果超过固定长度缓冲区,则从头开始。这是存储“最后N个”项目的节省空间和时间的方法。

现在,在您的情况下,您可以拥有一个1,000个计数器的循环缓冲区,每个计数器计算一毫秒内的消息数。添加所有1,000个计数器可以为您提供上一秒的总计数。当然,您可以通过逐步更新计数来优化报告部分,即从您插入时覆盖的数字中扣除计数,然后添加新数字。

然后,您可以拥有另一个具有60个插槽的循环缓冲区,并在整秒内计算消息的总数;每秒一次,取出毫秒缓冲区的总计数,并将计数写入分辨率为秒的缓冲区等。

这里是类似C的伪代码:

int msecbuf[1000]; // initialized with zeroes
int secbuf[60]; // ditto
int msecptr = 0, secptr = 0;
int count = 0;
int msec_total_ctr = 0;
void msg_received() { count++; }
void every_msec() {
  msec_total_ctr -= msecbuf[msecptr];
  msecbuf[msecptr] = count;
  msec_total_ctr += msecbuf[msecptr];
  count = 0;
  msecptr = (msecptr + 1) % 1000;
}
void every_sec() {
  secbuf[secptr] = msec_total_ctr;
  secptr = (secptr + 1) % 60;
}

答案 1 :(得分:8)

您想要exponential smoothing,也称为指数加权移动平均线。获取自上次消息到达后的时间的EWMA,然后将该时间划分为秒。您可以使用不同的权重运行其中几个以有效地覆盖更长的时间间隔。实际上,您使用的是一个无限长的窗口,因此您不必担心数据过期;减重为你做的。

答案 2 :(得分:3)

对于最后一个毫米绳,保持计数。当millisecord切片进入下一个切片时,重置计数并将计数添加到毫秒滚动缓冲器阵列。如果你保留这个累积量,你可以用固定数量的内存提取消息数/秒。

当完成0,1秒切片(或1分钟旁边的一些其他小值)时,将滚动缓冲器阵列中的最后0,1 * 1000个项加起来并将其放在下一个滚动缓冲区中。通过这种方式,您可以保持毫秒级滚动缓冲区较小(1000个项目,最大1s查找)和缓冲区以便查找分钟(600个项目)。

你可以用0,1分钟的间隔完成下一个技巧。所有问题都可以通过求和(或使用累积,减去两个值)几个整数来查询。

唯一的缺点是最后一个秒值每隔0,1分钟每隔ms和每分钟值改变一次,而小时值(以及最后半小时%的导数)每0.1分钟改变一次。但至少你要记住你的内存使用情况。

答案 3 :(得分:2)

您的滚动显示窗口只能更快地更新,假设您想要每秒更新10次,因此对于1秒钟的数据,您需要10个值。每个值将包含在1/10秒内显示的消息数。让我们称这些值为bin,每个bin保存1/10秒的数据。每100毫秒,其中一个垃圾箱被丢弃,一个新的垃圾箱被设置为在100毫秒内出现的消息数。

如果您希望在整个小时内保持1/10秒的精度,则需要一个36K的数组来保存一小时的有关您的信息速率的信息。但这似乎有点矫枉过正。

但是我认为随着时间间隔变大,精度下降会更合理。

也许你将1秒钟的数据精确到100毫秒,1分钟的数据精确到秒,1小时的数据精确到分钟,依此类推。

答案 4 :(得分:1)

  

我想到维护所有消息的队列,搜索超过一秒的最年轻的消息,并从索引中推断出计数。然而,这似乎太慢了,会耗费大量的记忆。

更好的想法是维护消息的链接列表,向头部添加新消息(带有时间戳),并在它们到期时从尾部弹出它们。或者甚至不弹出它们 - 只需保留指向所需时间范围内最早的消息的指针,并在该消息到期时将其推向头部(这允许您跟踪多个时间帧与一个列表)。

您可以在需要时通过从尾部走到头部来计算计数,或者只是单独存储计数,每当向头部添加值时递增计数,并在每次推进尾部时递减计数。