Skip to content

Commit

Permalink
chore: Extract roc_stat module
Browse files Browse the repository at this point in the history
Classes:
 - stat::MovAggregate (formerly MovStats)
 - stat::MovHistogram
 - stat::MovQuantile
  • Loading branch information
gavv committed Aug 1, 2024
1 parent 2314fa4 commit 7df7c68
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 156 deletions.
1 change: 1 addition & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ env['ROC_SOVER'] = '.'.join(env['ROC_VERSION'].split('.')[:2])
env['ROC_MODULES'] = [
'roc_core',
'roc_status',
'roc_stat',
'roc_dbgio',
'roc_address',
'roc_packet',
Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/internals/code_structure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Here is the full list of available modules:
module description
================= =================================
`roc_core`_ General-purpose building blocks (containers, memory management, multithreading, etc)
`roc_stat`_ Statistical functions
`roc_status`_ Status codes
`roc_address`_ Network URIs and addresses
`roc_packet`_ Network packets and packet processing
Expand Down
6 changes: 6 additions & 0 deletions src/internal_modules/main.dox
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ namespace roc {
namepsace core {}
}

namespace roc {
//! @namespace roc::stat
//! Statistical functions.
namepsace stat {}
}

namespace roc {
//! @namespace roc::status
//! Status codes.
Expand Down
4 changes: 2 additions & 2 deletions src/internal_modules/roc_rtp/link_meter.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#include "roc_audio/latency_config.h"
#include "roc_audio/sample_spec.h"
#include "roc_core/iarena.h"
#include "roc_core/mov_stats.h"
#include "roc_core/noncopyable.h"
#include "roc_core/time.h"
#include "roc_dbgio/csv_dumper.h"
Expand All @@ -24,6 +23,7 @@
#include "roc_rtcp/reports.h"
#include "roc_rtp/encoding.h"
#include "roc_rtp/encoding_map.h"
#include "roc_stat/mov_aggregate.h"

namespace roc {
namespace rtp {
Expand Down Expand Up @@ -108,7 +108,7 @@ class LinkMeter : public packet::ILinkMeter,
core::nanoseconds_t prev_queue_timestamp_;
packet::stream_timestamp_t prev_stream_timestamp_;

core::MovStats<core::nanoseconds_t> packet_jitter_stats_;
stat::MovAggregate<core::nanoseconds_t> packet_jitter_stats_;

dbgio::CsvDumper* dumper_;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
/*
* Copyright (c) 2023 Roc Streaming authors
* Copyright (c) 2024 Roc Streaming authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

//! @file roc_core/mov_stats.h
//! @brief Rolling window moving average and variance.
//! @file roc_stat/mov_aggregate.h
//! @brief Rolling window moving average, variance, minimum, and maximum.

#ifndef ROC_CORE_MOV_STATS_H_
#define ROC_CORE_MOV_STATS_H_
#ifndef ROC_STAT_MOV_AGGREGATE_H_
#define ROC_STAT_MOV_AGGREGATE_H_

#include "roc_core/array.h"
#include "roc_core/iarena.h"
#include "roc_core/panic.h"
#include "roc_core/ring_queue.h"

namespace roc {
namespace core {

//! Rolling window moving average and variance.
namespace stat {

//! Rolling window moving average, variance, minimum, and maximum.
//!
//! Efficiently implements moving average and variance based on approach
//! described in https://www.dsprelated.com/showthread/comp.dsp/97276-1.php,
//! and moving minimum/maximum based on "sorted deque" algorithm from
//! https://www.geeksforgeeks.org/sliding-window-maximum-maximum-of-all-subarrays-of-size-k/.
//!
//! @tparam T defines a sample type.
//! @remarks
//! Efficiently implements moving average and variance based on approach
//! described in https://www.dsprelated.com/showthread/comp.dsp/97276-1.php
template <typename T> class MovStats {
template <typename T> class MovAggregate {
public:
//! Initialize.
MovStats(IArena& arena, const size_t win_len)
MovAggregate(core::IArena& arena, const size_t win_len)
: buffer_(arena)
, buffer2_(arena)
, win_len_(win_len)
Expand Down Expand Up @@ -67,28 +70,9 @@ template <typename T> class MovStats {
return valid_;
}

//! Shift rolling window by one sample x.
void add(const T& x) {
const T x2 = x * x;
const T x_old = buffer_[buffer_i_];
buffer_[buffer_i_] = x;
const T x2_old = buffer2_[buffer_i_];
buffer2_[buffer_i_] = x2;

movsum_ += x - x_old;
movsum2_ += x2 - x2_old;

buffer_i_++;
if (buffer_i_ == win_len_) {
buffer_i_ = 0;
full_ = true;
}

slide_max_(x, x_old);
slide_min_(x, x_old);
}

//! Get moving average value.
//! @note
//! Has O(1) complexity.
T mov_avg() const {
T n = 0;
if (full_) {
Expand All @@ -102,6 +86,8 @@ template <typename T> class MovStats {
}

//! Get variance.
//! @note
//! Has O(1) complexity.
T mov_var() const {
T n = 0;
if (full_) {
Expand All @@ -114,14 +100,41 @@ template <typename T> class MovStats {
return (T)sqrt((n * movsum2_ - movsum_ * movsum_) / (n * n));
}

//! Min value in sliding window.
//! @note
//! Has O(1) complexity.
T mov_min() const {
return curr_min_;
}

//! Max value in sliding window.
//! @note
//! Has O(1) complexity.
T mov_max() const {
return curr_max_;
}

//! Min value in sliding window.
T mov_min() const {
return curr_min_;
//! Shift rolling window by one sample x.
//! @note
//! Has O(win_len) complexity.
void add(const T& x) {
const T x2 = x * x;
const T x_old = buffer_[buffer_i_];
buffer_[buffer_i_] = x;
const T x2_old = buffer2_[buffer_i_];
buffer2_[buffer_i_] = x2;

movsum_ += x - x_old;
movsum2_ += x2 - x2_old;

buffer_i_++;
if (buffer_i_ == win_len_) {
buffer_i_ = 0;
full_ = true;
}

slide_max_(x, x_old);
slide_min_(x, x_old);
}

//! Extend rolling window length.
Expand Down Expand Up @@ -158,9 +171,8 @@ template <typename T> class MovStats {
private:
//! Keeping a sliding max by using a sorted deque.
//! @remarks
//! The wedge is always sorted in descending order.
//! The current max is always at the front of the wedge.
//! https://www.geeksforgeeks.org/sliding-window-maximum-maximum-of-all-subarrays-of-size-k/
//! The wedge is always sorted in descending order.
//! The current max is always at the front of the wedge.
void slide_max_(const T& x, const T x_old) {
if (queue_max_.is_empty()) {
queue_max_.push_back(x);
Expand All @@ -182,9 +194,8 @@ template <typename T> class MovStats {

//! Keeping a sliding min by using a sorted deque.
//! @remarks
//! The wedge is always sorted in ascending order.
//! The current min is always at the front of the wedge.
//! https://www.geeksforgeeks.org/sliding-window-maximum-maximum-of-all-subarrays-of-size-k/
//! The wedge is always sorted in ascending order.
//! The current min is always at the front of the wedge.
void slide_min_(const T& x, const T x_old) {
if (queue_min_.is_empty()) {
queue_min_.push_back(x);
Expand All @@ -204,8 +215,8 @@ template <typename T> class MovStats {
}
}

Array<T> buffer_;
Array<T> buffer2_;
core::Array<T> buffer_;
core::Array<T> buffer2_;

const size_t win_len_;
size_t buffer_i_;
Expand All @@ -218,15 +229,15 @@ template <typename T> class MovStats {
bool full_;
bool first_;

RingQueue<T> queue_max_;
core::RingQueue<T> queue_max_;
T curr_max_;
RingQueue<T> queue_min_;
core::RingQueue<T> queue_min_;
T curr_min_;

bool valid_;
};

} // namespace core
} // namespace stat
} // namespace roc

#endif // ROC_CORE_MOV_STATS_H_
#endif // ROC_STAT_MOV_AGGREGATE_H_
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

//! @file roc_core/mov_histogram.h
//! @file roc_stat/mov_histogram.h
//! @brief Rolling window moving histogram.

#ifndef ROC_CORE_MOV_HISTOGRAM_H_
#define ROC_CORE_MOV_HISTOGRAM_H_
#ifndef ROC_STAT_MOV_HISTOGRAM_H_
#define ROC_STAT_MOV_HISTOGRAM_H_

#include "roc_core/array.h"
#include "roc_core/iarena.h"
#include "roc_core/panic.h"
#include "roc_core/ring_queue.h"

namespace roc {
namespace core {
namespace stat {

//! Rolling window histogram.
//!
//! The MovHistogram class maintains a histogram of values within a specified window
//! length. It divides the range of values into a specified number of bins and updates the
//! histogram as new values are added and old values are removed from the window.
//!
//! Similar to MovQuantile, this class also is capable of computing moving quantiles.
//! MovHistogram is generally faster than MovQuantile, but has several restrictions:
//! - value range should be limited and relatively small compared to the bin size;
//! you need either small range or large bins
//! - calculated quantile is only an approximation, and error depends on bin size;
//! you need small bins for better precision
//! - calculation of quantile has O(N) complexity based on the number of bins;
//! you need lesser bins to keep it fast
//!
//! @tparam T The type of values to be histogrammed.
template <typename T> class MovHistogram {
public:
Expand All @@ -38,7 +47,7 @@ template <typename T> class MovHistogram {
//! subrange of the value range.
//! @param window_length The length of the moving window. Only values within this
//! window are considered in the histogram.
MovHistogram(IArena& arena,
MovHistogram(core::IArena& arena,
T value_range_min,
T value_range_max,
size_t num_bins,
Expand Down Expand Up @@ -71,11 +80,15 @@ template <typename T> class MovHistogram {
}

//! Get the number of values in the given bin.
//! @note
//! Has O(1) complexity.
size_t mov_counter(size_t bin_index) const {
return bins_[bin_index];
}

//! Add a value to the histogram.
//! @note
//! Has O(1) complexity.
void add(const T& value) {
T clamped_value = value;

Expand All @@ -86,14 +99,14 @@ template <typename T> class MovHistogram {
}

if (ring_buffer_.size() == window_length_) {
T oldest_value = ring_buffer_.front();
const T oldest_value = ring_buffer_.front();
ring_buffer_.pop_front();
size_t oldest_bin_index = get_bin_index_(oldest_value);
const size_t oldest_bin_index = get_bin_index_(oldest_value);
bins_[oldest_bin_index]--;
}

ring_buffer_.push_back(clamped_value);
size_t new_bin_index = get_bin_index_(clamped_value);
const size_t new_bin_index = get_bin_index_(clamped_value);
if (new_bin_index < num_bins_) {
bins_[new_bin_index]++;
}
Expand All @@ -109,17 +122,19 @@ template <typename T> class MovHistogram {
return size_t((value - value_range_min_) / bin_width_);
}

T value_range_min_;
T value_range_max_;
size_t num_bins_;
size_t window_length_;
const T value_range_min_;
const T value_range_max_;
const size_t num_bins_;
const size_t window_length_;
T bin_width_;
RingQueue<T> ring_buffer_;
Array<size_t> bins_;

core::RingQueue<T> ring_buffer_;
core::Array<size_t> bins_;

bool valid_;
};

} // namespace core
} // namespace stat
} // namespace roc

#endif // ROC_CORE_MOV_HISTOGRAM_H_
#endif // ROC_STAT_MOV_HISTOGRAM_H_
Loading

0 comments on commit 7df7c68

Please sign in to comment.