added RingBufferHistory template class, used it in SentPacketHistory and MovingMinMaxAvg

This commit is contained in:
wangyix 2014-07-09 15:38:03 -07:00
parent a8cf199256
commit 81e168f657
5 changed files with 234 additions and 135 deletions

View file

@ -14,8 +14,6 @@
SentPacketHistory::SentPacketHistory(int size)
: _sentPackets(size),
_newestPacketAt(0),
_numExistingPackets(0),
_newestSequenceNumber(std::numeric_limits<uint16_t>::max())
{
}
@ -29,16 +27,8 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const QByteArray& pa
qDebug() << "Unexpected sequence number passed to SentPacketHistory::packetSent()!"
<< "Expected:" << expectedSequenceNumber << "Actual:" << sequenceNumber;
}
_newestSequenceNumber = sequenceNumber;
// increment _newestPacketAt cyclically, insert new packet there.
// this will overwrite the oldest packet in the buffer
_newestPacketAt = (_newestPacketAt == _sentPackets.size() - 1) ? 0 : _newestPacketAt + 1;
_sentPackets[_newestPacketAt] = packet;
if (_numExistingPackets < _sentPackets.size()) {
_numExistingPackets++;
}
_sentPackets.insert(packet);
}
const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const {
@ -51,13 +41,6 @@ const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const {
if (seqDiff < 0) {
seqDiff += UINT16_RANGE;
}
// if desired sequence number is too old to be found in the history, return null
if (seqDiff >= _numExistingPackets) {
return NULL;
}
int packetAt = _newestPacketAt - seqDiff;
if (packetAt < 0) {
packetAt += _sentPackets.size();
}
return &_sentPackets.at(packetAt);
return _sentPackets.get(seqDiff);
}

View file

@ -13,7 +13,7 @@
#include <stdint.h>
#include <qbytearray.h>
#include <qvector.h>
#include "RingBufferHistory.h"
#include "SequenceNumberStats.h"
@ -26,9 +26,7 @@ public:
const QByteArray* getPacket(uint16_t sequenceNumber) const;
private:
QVector<QByteArray> _sentPackets; // circular buffer
int _newestPacketAt;
int _numExistingPackets;
RingBufferHistory<QByteArray> _sentPackets; // circular buffer
uint16_t _newestSequenceNumber;
};

View file

@ -12,9 +12,48 @@
#ifndef hifi_MovingMinMaxAvg_h
#define hifi_MovingMinMaxAvg_h
#include "RingBufferHistory.h"
template <typename T>
class MovingMinMaxAvg {
private:
class Stats {
public:
Stats()
: _min(std::numeric_limits<T>::max()),
_max(std::numeric_limits<T>::min()),
_average(0.0) {}
void updateWithSample(T sample, int& numSamplesInAverage) {
if (sample < _min) {
_min = sample;
}
if (sample > _max) {
_max = sample;
}
_average = _average * ((double)numSamplesInAverage / (numSamplesInAverage + 1))
+ (double)sample / (numSamplesInAverage + 1);
numSamplesInAverage++;
}
void updateWithOtherStats(const Stats& other, int& numStatsInAverage) {
if (other._min < _min) {
_min = other._min;
}
if (other._max > _max) {
_max = other._max;
}
_average = _average * ((double)numStatsInAverage / (numStatsInAverage + 1))
+ other._average / (numStatsInAverage + 1);
numStatsInAverage++;
}
T _min;
T _max;
double _average;
};
public:
// This class collects 3 stats (min, max, avg) over a moving window of samples.
// The moving window contains _windowIntervals * _intervalLength samples.
@ -25,104 +64,51 @@ public:
// new sample, instantiate this class with MovingMinMaxAvg(1, 100).
MovingMinMaxAvg(int intervalLength, int windowIntervals)
: _min(std::numeric_limits<T>::max()),
_max(std::numeric_limits<T>::min()),
_average(0.0),
_samplesCollected(0),
_intervalLength(intervalLength),
: _intervalLength(intervalLength),
_windowIntervals(windowIntervals),
_overallStats(),
_samplesCollected(0),
_windowStats(),
_existingSamplesInCurrentInterval(0),
_existingIntervals(0),
_windowMin(std::numeric_limits<T>::max()),
_windowMax(std::numeric_limits<T>::min()),
_windowAverage(0.0),
_currentIntervalMin(std::numeric_limits<T>::max()),
_currentIntervalMax(std::numeric_limits<T>::min()),
_currentIntervalAverage(0.0),
_newestIntervalStatsAt(0),
_currentIntervalStats(),
_intervalStats(windowIntervals),
_newStatsAvailable(false)
{
_intervalMins = new T[_windowIntervals];
_intervalMaxes = new T[_windowIntervals];
_intervalAverages = new double[_windowIntervals];
}
~MovingMinMaxAvg() {
delete[] _intervalMins;
delete[] _intervalMaxes;
delete[] _intervalAverages;
}
{}
void reset() {
_min = std::numeric_limits<T>::max();
_max = std::numeric_limits<T>::min();
_average = 0.0;
_overallStats = Stats();
_samplesCollected = 0;
_windowStats = Stats();
_existingSamplesInCurrentInterval = 0;
_existingIntervals = 0;
_windowMin = std::numeric_limits<T>::max();
_windowMax = std::numeric_limits<T>::min();
_windowAverage = 0.0;
_currentIntervalMin = std::numeric_limits<T>::max();
_currentIntervalMax = std::numeric_limits<T>::min();
_currentIntervalAverage = 0.0;
_newStatsAvailableFlag = false;
_currentIntervalStats = Stats();
_intervalStats.clear();
_newStatsAvailable = false;
}
void update(T newSample) {
// update overall stats
if (newSample < _min) {
_min = newSample;
}
if (newSample > _max) {
_max = newSample;
}
updateAverage(_average, _samplesCollected, (double)newSample);
_overallStats.updateWithSample(newSample, _samplesCollected);
// update the current interval stats
if (newSample < _currentIntervalMin) {
_currentIntervalMin = newSample;
}
if (newSample > _currentIntervalMax) {
_currentIntervalMax = newSample;
}
updateAverage(_currentIntervalAverage, _existingSamplesInCurrentInterval, (double)newSample);
_currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval);
// if the current interval of samples is now full, record its stats into our past intervals' stats
if (_existingSamplesInCurrentInterval == _intervalLength) {
// increment index of the newest interval's stats cyclically
_newestIntervalStatsAt = _newestIntervalStatsAt == _windowIntervals - 1 ? 0 : _newestIntervalStatsAt + 1;
// record current interval's stats, then reset them
_intervalMins[_newestIntervalStatsAt] = _currentIntervalMin;
_intervalMaxes[_newestIntervalStatsAt] = _currentIntervalMax;
_intervalAverages[_newestIntervalStatsAt] = _currentIntervalAverage;
_currentIntervalMin = std::numeric_limits<T>::max();
_currentIntervalMax = std::numeric_limits<T>::min();
_currentIntervalAverage = 0.0;
_intervalStats.insert(_currentIntervalStats);
_currentIntervalStats = Stats();
_existingSamplesInCurrentInterval = 0;
if (_existingIntervals < _windowIntervals) {
_existingIntervals++;
}
// update the window's stats
int k = _newestIntervalStatsAt;
_windowMin = _intervalMins[k];
_windowMax = _intervalMaxes[k];
_windowAverage = _intervalAverages[k];
int intervalsIncludedInWindowStats = 1;
while (intervalsIncludedInWindowStats < _existingIntervals) {
k = k == 0 ? _windowIntervals - 1 : k - 1;
if (_intervalMins[k] < _windowMin) {
_windowMin = _intervalMins[k];
}
if (_intervalMaxes[k] > _windowMax) {
_windowMax = _intervalMaxes[k];
}
updateAverage(_windowAverage, intervalsIncludedInWindowStats, _intervalAverages[k]);
// update the window's stats by combining the intervals' stats
RingBufferHistory<Stats>::Iterator i = _intervalStats.begin();
RingBufferHistory<Stats>::Iterator end = _intervalStats.end();
_windowStats = Stats();
int intervalsIncludedInWindowStats = 0;
while (i != end) {
_windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats);
i++;
}
_newStatsAvailable = true;
@ -133,48 +119,34 @@ public:
bool getNewStatsAvailableFlag() const { return _newStatsAvailable; }
void clearNewStatsAvailableFlag() { _newStatsAvailable = false; }
T getMin() const { return _min; }
T getMax() const { return _max; }
double getAverage() const { return _average; }
T getWindowMin() const { return _windowMin; }
T getWindowMax() const { return _windowMax; }
double getWindowAverage() const { return _windowAverage; }
T getMin() const { return _overallStats._min; }
T getMax() const { return _overallStats._max; }
double getAverage() const { return _overallStats._average; }
T getWindowMin() const { return _windowStats._min; }
T getWindowMax() const { return _windowStats._max; }
double getWindowAverage() const { return _windowStats._average; }
private:
void updateAverage(double& average, int& numSamples, double newSample) {
// update some running average without overflowing it
average = average * ((double)numSamples / (numSamples + 1)) + newSample / (numSamples + 1);
numSamples++;
}
private:
// these are min/max/avg stats for all samples collected.
T _min;
T _max;
double _average;
int _samplesCollected;
int _intervalLength;
int _windowIntervals;
int _existingSamplesInCurrentInterval;
int _existingIntervals;
// these are min/max/avg stats for all samples collected.
Stats _overallStats;
int _samplesCollected;
// these are the min/max/avg stats for the samples in the moving window
T _windowMin;
T _windowMax;
double _windowAverage;
Stats _windowStats;
int _existingSamplesInCurrentInterval;
T _currentIntervalMin;
T _currentIntervalMax;
double _currentIntervalAverage;
// these are the min/max/avg stats for the current interval
Stats _currentIntervalStats;
T* _intervalMins;
T* _intervalMaxes;
double* _intervalAverages;
int _newestIntervalStatsAt;
// these are stored stats for the past intervals in the window
RingBufferHistory<Stats> _intervalStats;
bool _newStatsAvailable;
};
#endif // hifi_OctalCode_h
#endif // hifi_MovingMinMaxAvg_h

View file

@ -0,0 +1,146 @@
//
// RingBufferHistory.h
// libraries/shared/src
//
// Created by Yixin Wang on 7/9/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RingBufferHistory_h
#define hifi_RingBufferHistory_h
#include <stdlib.h>
#include <iterator>
template <typename T>
class RingBufferHistory {
public:
RingBufferHistory(int capacity)
: _size(capacity + 1),
_capacity(capacity),
_newestEntryAt(0),
_numEntries(0)
{
_buffer = new T[_size];
}
RingBufferHistory(const RingBufferHistory& other)
: _size(other._size),
_capacity(other._capacity),
_newestEntryAt(other._newestEntryAt),
_numEntries(other._numEntries)
{
_buffer = new T[_size];
memcpy(_buffer, other._buffer, _size*sizeof(T));
}
RingBufferHistory& operator= (const RingBufferHistory& rhs) {
_size = rhs._size;
_capacity = rhs._capacity;
_newestEntryAt = rhs._newestEntryAt;
_numEntries = rhs._numEntries;
delete[] _buffer;
_buffer = new T[_size];
memcpy(_buffer, rhs._buffer, _size*sizeof(T));
}
~RingBufferHistory() {
delete[] _buffer;
}
void clear() {
_numEntries = 0;
}
void insert(const T& entry) {
// increment newest entry index cyclically
_newestEntryAt = (_newestEntryAt == _size - 1) ? 0 : _newestEntryAt + 1;
// insert new entry
_buffer[_newestEntryAt] = entry;
if (_numEntries < _capacity) {
_numEntries++;
}
}
// 0 retrieves the most recent entry, _numEntries - 1 retrieves the oldest.
// returns NULL if entryAge not within [0, _numEntries-1]
const T* get(int entryAge) const {
if (!(entryAge >= 0 && entryAge < _numEntries)) {
return NULL;
}
int entryAt = _newestEntryAt - entryAge;
if (entryAt < 0) {
entryAt += _size;
}
return &_buffer[entryAt];
}
T* get(int entryAge) {
return const_cast<T*>((static_cast<const RingBufferHistory*>(this))->get(entryAge));
}
const T* getNewestEntry() const {
return &_buffer[_newestEntryAt];
}
T* getNewestEntry() {
return &_buffer[_newestEntryAt];
}
int getCapacity() const { return _capacity; }
int getNumEntries() const { return _numEntries; }
private:
T* _buffer;
int _size;
int _capacity;
int _newestEntryAt;
int _numEntries;
public:
class Iterator : public std::iterator < std::forward_iterator_tag, T > {
public:
Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _at(at) {}
bool operator==(const Iterator& rhs) { return _at == rhs._at; }
bool operator!=(const Iterator& rhs) { return _at != rhs._at; }
T& operator*() { return *_at; }
T* operator->() { return _at; }
Iterator& operator++() {
_at = (_at == _buffer) ? _bufferEnd - 1 : _at - 1;
return *this;
}
Iterator& operator++(int) {
Iterator tmp(*this);
++(*this);
return tmp;
}
private:
T* const _buffer;
T* const _bufferEnd;
T* _at;
};
Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAt]); }
Iterator end() {
int endAt = _newestEntryAt - _numEntries;
if (endAt < 0) {
endAt += _size;
}
return Iterator(_buffer, _size, &_buffer[endAt]);
}
};
#endif // hifi_RingBufferHistory_h

View file

@ -59,7 +59,7 @@ void MovingMinMaxAvgTests::runAllTests() {
assert(stats.getMin() == min);
assert(stats.getMax() == max);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001);
if ((i + 1) % INTERVAL_LENGTH == 0) {
@ -78,7 +78,7 @@ void MovingMinMaxAvgTests::runAllTests() {
assert(stats.getWindowMin() == windowMin);
assert(stats.getWindowMax() == windowMax);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001);
} else {
assert(!stats.getNewStatsAvailableFlag());