Faster implementation of BitRateStats.
Landing cl for hguihot. At high bitrate, EraseOld() could account for a significant part of the total CPU usage on certain platforms. The new implementation eliminates per-packet memory allocations and records the number of bytes in buckets (one bucket per millisecond in window). R=stefan@webrtc.org Review URL: https://webrtc-codereview.appspot.com/4269004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5175 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
326bcff879
commit
d89b52af80
@ -12,52 +12,73 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const float kBitrateAverageWindowMs = 500.0f;
|
||||
const int kBitrateAverageWindowMs = 500;
|
||||
|
||||
BitRateStats::BitRateStats()
|
||||
: data_samples_(),
|
||||
accumulated_bytes_(0) {
|
||||
: num_buckets_(kBitrateAverageWindowMs + 1), // N ms in (N+1) buckets.
|
||||
buckets_(new uint32_t[num_buckets_]()),
|
||||
accumulated_bytes_(0),
|
||||
oldest_time_(0),
|
||||
oldest_index_(0),
|
||||
bps_coefficient_(8.f * 1000.f / (num_buckets_ - 1)) {
|
||||
}
|
||||
|
||||
BitRateStats::~BitRateStats() {
|
||||
Init();
|
||||
}
|
||||
|
||||
void BitRateStats::Init() {
|
||||
accumulated_bytes_ = 0;
|
||||
while (data_samples_.size() > 0) {
|
||||
delete data_samples_.front();
|
||||
data_samples_.pop_front();
|
||||
oldest_time_ = 0;
|
||||
oldest_index_ = 0;
|
||||
for (int i = 0; i < num_buckets_; i++) {
|
||||
buckets_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BitRateStats::Update(uint32_t packet_size_bytes, int64_t now_ms) {
|
||||
// Find an empty slot for storing the new sample and at the same time
|
||||
// accumulate the history.
|
||||
data_samples_.push_back(new DataTimeSizeTuple(packet_size_bytes, now_ms));
|
||||
accumulated_bytes_ += packet_size_bytes;
|
||||
EraseOld(now_ms);
|
||||
}
|
||||
|
||||
void BitRateStats::EraseOld(int64_t now_ms) {
|
||||
while (data_samples_.size() > 0) {
|
||||
if (now_ms - data_samples_.front()->time_complete_ms >
|
||||
kBitrateAverageWindowMs) {
|
||||
// Delete old sample
|
||||
accumulated_bytes_ -= data_samples_.front()->size_bytes;
|
||||
delete data_samples_.front();
|
||||
data_samples_.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (now_ms < oldest_time_) {
|
||||
// Too old data is ignored.
|
||||
return;
|
||||
}
|
||||
|
||||
EraseOld(now_ms);
|
||||
|
||||
int now_offset = static_cast<int>(now_ms - oldest_time_);
|
||||
assert(now_offset < num_buckets_);
|
||||
int index = oldest_index_ + now_offset;
|
||||
if (index >= num_buckets_) {
|
||||
index -= num_buckets_;
|
||||
}
|
||||
buckets_[index] += packet_size_bytes;
|
||||
accumulated_bytes_ += packet_size_bytes;
|
||||
}
|
||||
|
||||
uint32_t BitRateStats::BitRate(int64_t now_ms) {
|
||||
// Calculate the average bit rate the past BITRATE_AVERAGE_WINDOW ms.
|
||||
// Removes any old samples from the list.
|
||||
EraseOld(now_ms);
|
||||
return static_cast<uint32_t>(accumulated_bytes_ * 8.0f * 1000.0f /
|
||||
kBitrateAverageWindowMs + 0.5f);
|
||||
return static_cast<uint32_t>(accumulated_bytes_ * bps_coefficient_ + 0.5f);
|
||||
}
|
||||
|
||||
void BitRateStats::EraseOld(int64_t now_ms) {
|
||||
int64_t new_oldest_time = now_ms - num_buckets_ + 1;
|
||||
if (new_oldest_time <= oldest_time_) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (oldest_time_ < new_oldest_time) {
|
||||
uint32_t num_bytes_in_oldest_bucket = buckets_[oldest_index_];
|
||||
assert(accumulated_bytes_ >= num_bytes_in_oldest_bucket);
|
||||
accumulated_bytes_ -= num_bytes_in_oldest_bucket;
|
||||
buckets_[oldest_index_] = 0;
|
||||
if (++oldest_index_ >= num_buckets_) {
|
||||
oldest_index_ = 0;
|
||||
}
|
||||
++oldest_time_;
|
||||
if (accumulated_bytes_ == 0) {
|
||||
// This guarantees we go through all the buckets at most once, even if
|
||||
// |new_oldest_time| is far greater than |oldest_time_|.
|
||||
break;
|
||||
}
|
||||
}
|
||||
oldest_time_ = new_oldest_time;
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
@ -11,8 +11,7 @@
|
||||
#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_BITRATE_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
@ -27,20 +26,24 @@ class BitRateStats {
|
||||
uint32_t BitRate(int64_t now_ms);
|
||||
|
||||
private:
|
||||
struct DataTimeSizeTuple {
|
||||
DataTimeSizeTuple(uint32_t size_bytes_in, int64_t time_complete_ms_in)
|
||||
: size_bytes(size_bytes_in),
|
||||
time_complete_ms(time_complete_ms_in) {
|
||||
}
|
||||
|
||||
uint32_t size_bytes;
|
||||
int64_t time_complete_ms;
|
||||
};
|
||||
|
||||
void EraseOld(int64_t now_ms);
|
||||
|
||||
std::list<DataTimeSizeTuple*> data_samples_;
|
||||
// Numbers of bytes are kept in buckets (circular buffer), with one bucket
|
||||
// per millisecond.
|
||||
const int num_buckets_;
|
||||
scoped_array<uint32_t> buckets_;
|
||||
|
||||
// Total number of bytes recorded in buckets.
|
||||
uint32_t accumulated_bytes_;
|
||||
|
||||
// Oldest time recorded in buckets.
|
||||
int64_t oldest_time_;
|
||||
|
||||
// Bucket index of oldest bytes recorded in buckets.
|
||||
int oldest_index_;
|
||||
|
||||
// To convert number of bytes in bits/second.
|
||||
const float bps_coefficient_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
|
@ -17,7 +17,7 @@ using webrtc::BitRateStats;
|
||||
|
||||
class BitRateStatsTest : public ::testing::Test {
|
||||
protected:
|
||||
BitRateStatsTest() {};
|
||||
BitRateStatsTest() {}
|
||||
BitRateStats stats_;
|
||||
};
|
||||
|
||||
@ -47,4 +47,51 @@ TEST_F(BitRateStatsTest, TestStrictMode) {
|
||||
// the estimate should be 0.
|
||||
EXPECT_EQ(0u, stats_.BitRate(now_ms));
|
||||
}
|
||||
|
||||
TEST_F(BitRateStatsTest, IncreasingThenDecreasingBitrate) {
|
||||
int64_t now_ms = 0;
|
||||
stats_.Init();
|
||||
// Expecting 0 after init.
|
||||
uint32_t bitrate = stats_.BitRate(now_ms);
|
||||
EXPECT_EQ(0u, bitrate);
|
||||
// 1000 bytes per millisecond until plateau is reached.
|
||||
while (++now_ms < 10000) {
|
||||
stats_.Update(1000, now_ms);
|
||||
uint32_t new_bitrate = stats_.BitRate(now_ms);
|
||||
if (new_bitrate != bitrate) {
|
||||
// New bitrate must be higher than previous one.
|
||||
EXPECT_GT(new_bitrate, bitrate);
|
||||
} else {
|
||||
// Plateau reached, 8000 kbps expected.
|
||||
EXPECT_NEAR(8000000u, bitrate, 80000u);
|
||||
break;
|
||||
}
|
||||
bitrate = new_bitrate;
|
||||
}
|
||||
// 1000 bytes per millisecond until 10-second mark, 8000 kbps expected.
|
||||
while (++now_ms < 10000) {
|
||||
stats_.Update(1000, now_ms);
|
||||
bitrate = stats_.BitRate(now_ms);
|
||||
EXPECT_NEAR(8000000u, bitrate, 80000u);
|
||||
}
|
||||
// Zero bytes per millisecond until 0 is reached.
|
||||
while (++now_ms < 20000) {
|
||||
stats_.Update(0, now_ms);
|
||||
uint32_t new_bitrate = stats_.BitRate(now_ms);
|
||||
if (new_bitrate != bitrate) {
|
||||
// New bitrate must be lower than previous one.
|
||||
EXPECT_LT(new_bitrate, bitrate);
|
||||
} else {
|
||||
// 0 kbps expected.
|
||||
EXPECT_EQ(0u, bitrate);
|
||||
break;
|
||||
}
|
||||
bitrate = new_bitrate;
|
||||
}
|
||||
// Zero bytes per millisecond until 20-second mark, 0 kbps expected.
|
||||
while (++now_ms < 20000) {
|
||||
stats_.Update(0, now_ms);
|
||||
EXPECT_EQ(0u, stats_.BitRate(now_ms));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
Loading…
x
Reference in New Issue
Block a user