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:
mikhal@webrtc.org 2013-11-25 17:49:28 +00:00
parent 326bcff879
commit d89b52af80
3 changed files with 114 additions and 43 deletions

View File

@ -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

View File

@ -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

View File

@ -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