实现"hits in last [second/minute/hour]"数据结构

Implementation of a "hits in last [second/minute/hour]" data structure

本文关键字:minute hour 数据结构 hits in last 实现 second      更新时间:2023-10-16

我认为这是一个相当常见的问题,但我似乎无法通过谷歌搜索找到答案(也许我不知道这个问题有一个更准确的名称?)

您需要使用用于报告命中的"hit()"方法和hitsInLastSecond|Minute|Hour方法来实现一个结构。你有一个精确到纳秒的计时器。您如何有效地实现这一点?

我的想法是这样的(在psuedo-C++中)

class HitCounter {
  void hit() {
    hits_at[now()] = ++last_count;
  }
  int hitsInLastSecond() {
    auto before_count = hits_at.lower_bound(now() - 1 * second)
    if (before_count == hits_at.end()) { return last_count; }
    return last_count - before_count->second;
  }
  // etc for Minute, Hour
  map<time_point, int> hits_at;
  int last_count = 0;
};

这行得通吗?它好吗?有更好的东西吗?

更新:根据评论添加了修剪并切换到deque:

class HitCounter {
  void hit() {
    hits.push_back(make_pair(now(), ++last_count));
  }
  int hitsInLastSecond() {
    auto before = lower_bound(hits.begin(), hits.end(), make_pair(now() - 1 * second, -1));
    if (before == hits.end()) { return last_count; }
    return last_count - before_count->second;
  }
  // etc for Minute, Hour
  void prune() {
    auto old = upper_bound(hits.begin(). hits.end(), make_pair(now - 1 * hour, -1));
    if (old != hits.end()) {
      hits.erase(hits.begin(), old)
    }
  }
  deqeue<pair<time_point, int>> hits;
  int last_count = 0;
};

您所描述的内容称为直方图。

如果你想要纳秒级的精度,那么使用哈希会消耗掉你的大部分cpu。您可能需要一个环形缓冲区来存储数据。

使用std::chrono来实现您所需的计时精度,但坦率地说,每秒的命中率似乎是您所需要的最高粒度,如果您着眼于全局,精度似乎并不重要。

这是一个部分的,介绍性的样本,你可以如何去做:

#include <array>
#include <algorithm>
template<size_t RingSize>
class Histogram
{
    std::array<size_t, RingSize> m_ringBuffer;
    size_t m_total;
    size_t m_position;
public:
    Histogram() : m_total(0)
    {
        std::fill_n(m_ringBuffer.begin(), RingSize, 0);
    }
    void addHit()
    {
        ++m_ringBuffer[m_position];
        ++m_total;
    }
    void incrementPosition()
    {
        if (++m_position >= RingSize)
            m_position = 0;
        m_total -= m_ringBuffer[m_position];
        m_ringBuffer[m_position] = 0;
    }
    double runningAverage() const
    {
        return (double)m_total / (double)RingSize;
    }
    size_t runningTotal() const { return m_total; }
};
Histogram<60> secondsHisto;
Histogram<60> minutesHisto;
Histogram<24> hoursHisto;
Histogram<7> weeksHisto;

这是一个简单的实现,假设您将每秒调用一次并递增位置,并将runningTotal从一个直方图转换到下一个直方图(因此,每隔60秒,将secondsHistor.runningTotal添加到minutesHisto)。

希望这将是一个有用的入门之地。

如果您想跟踪每秒点击量的较长直方图,可以使用此模型,通过增加环形大小,添加第二个总数来跟踪最后N个环形缓冲区条目,使m_subTotal=sum(m_ringBuffer[m_position-N..m_position]),类似于m_total的工作方式。

size_t m_10sTotal;
...
void addHit()
{
    ++m_ringBuffer[m_position];
    ++m_total;
    ++m_10sTotal;
}
void incrementPosition()
{
    // subtract data from >10 sample intervals ago.
    m_10sTotal -= m_ringBuffer[(m_position + RingBufferSize - 10) % RingBufferSize];
    // for the naive total, do the subtraction after we
    // advance position, since it will coincide with the
    // location of the value RingBufferSize ago.
    if (++m_position >= RingBufferSize)
        m_position = 0;
    m_total -= m_ringBuffer[m_position];
}

你不必把直方图做成这些大小,这只是一个简单的抓取模型。有多种选择,例如同时递增每个直方图:

secondsHisto.addHit();
minutesHisto.addHit();
hoursHisto.addHit();
weeksHisto.addHit();

每个滚动都是独立的,所以都有当前值。对每个历史记录进行大小调整,以使该粒度的数据能够返回。