dcsctp: Use integer math in RTO calculations

Following Congestion avoidance and control by V. Jacobson at
https://dl.acm.org/doi/10.1145/52324.52356, use integer math instead
of floating point. Not that it matters, but it results in some code size
savings, and is more efficient. Due to not using floating point math,
some golden values in test cases were rounded a bit differently.

Bug: webrtc:12614
Change-Id: I0b7d54b8fd9ce7156e6b2582437ef5720f8838ea
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231229
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34956}
This commit is contained in:
Victor Boivie 2021-09-06 22:16:09 +02:00 committed by WebRTC LUCI CQ
parent 211bf7b253
commit 3ed2b8d0b4
3 changed files with 37 additions and 45 deletions

View file

@ -9,17 +9,12 @@
*/
#include "net/dcsctp/tx/retransmission_timeout.h"
#include <cmath>
#include <algorithm>
#include <cstdint>
#include "net/dcsctp/public/dcsctp_options.h"
namespace dcsctp {
namespace {
// https://tools.ietf.org/html/rfc4960#section-15
constexpr double kRtoAlpha = 0.125;
constexpr double kRtoBeta = 0.25;
} // namespace
RetransmissionTimeout::RetransmissionTimeout(const DcSctpOptions& options)
: min_rto_(*options.rto_min),
@ -28,42 +23,39 @@ RetransmissionTimeout::RetransmissionTimeout(const DcSctpOptions& options)
rto_(*options.rto_initial) {}
void RetransmissionTimeout::ObserveRTT(DurationMs measured_rtt) {
double rtt = *measured_rtt;
int32_t rtt = *measured_rtt;
// Unrealistic values will be skipped. If a wrongly measured (or otherwise
// corrupt) value was processed, it could change the state in a way that would
// take a very long time to recover.
if (rtt < 0.0 || rtt > max_rtt_) {
if (rtt < 0 || rtt > max_rtt_) {
return;
}
// From https://tools.ietf.org/html/rfc4960#section-6.3.1, but avoiding
// floating point math by implementing algorithm from "V. Jacobson: Congestion
// avoidance and control", but adapted for SCTP.
if (first_measurement_) {
// https://tools.ietf.org/html/rfc4960#section-6.3.1
// "When the first RTT measurement R is made, set
// SRTT <- R,
// RTTVAR <- R/2, and
// RTO <- SRTT + 4 * RTTVAR."
srtt_ = rtt;
rttvar_ = rtt * 0.5;
rto_ = srtt_ + 4 * rttvar_;
scaled_srtt_ = rtt << kRttShift;
scaled_rtt_var_ = (rtt / 2) << kRttVarShift;
first_measurement_ = false;
} else {
// https://tools.ietf.org/html/rfc4960#section-6.3.1
// "When a new RTT measurement R' is made, set
// RTTVAR <- (1 - RTO.Beta) * RTTVAR + RTO.Beta * |SRTT - R'|
// SRTT <- (1 - RTO.Alpha) * SRTT + RTO.Alpha * R'
// RTO <- SRTT + 4 * RTTVAR."
rttvar_ = (1 - kRtoBeta) * rttvar_ + kRtoBeta * std::abs(srtt_ - rtt);
srtt_ = (1 - kRtoAlpha) * srtt_ + kRtoAlpha * rtt;
rto_ = srtt_ + 4 * rttvar_;
rtt -= (scaled_srtt_ >> kRttShift);
scaled_srtt_ += rtt;
if (rtt < 0) {
rtt = -rtt;
}
rtt -= (scaled_rtt_var_ >> kRttVarShift);
scaled_rtt_var_ += rtt;
}
rto_ = (scaled_srtt_ >> kRttShift) + scaled_rtt_var_;
// If the RTO becomes smaller or equal to RTT, expiration timers will be
// scheduled at the same time as packets are expected. Only happens in
// extremely stable RTTs, i.e. in simulations.
rto_ = std::fmax(rto_, rtt + 1);
rto_ = std::max(rto_, rtt + 1);
// Clamp RTO between min and max.
rto_ = std::fmin(std::fmax(rto_, min_rto_), max_rto_);
rto_ = std::min(std::max(rto_, min_rto_), max_rto_);
}
} // namespace dcsctp

View file

@ -27,6 +27,8 @@ namespace dcsctp {
// a lot, which is an indicator of a bad connection.
class RetransmissionTimeout {
public:
static constexpr int kRttShift = 3;
static constexpr int kRttVarShift = 2;
explicit RetransmissionTimeout(const DcSctpOptions& options);
// To be called when a RTT has been measured, to update the RTO value.
@ -36,22 +38,20 @@ class RetransmissionTimeout {
DurationMs rto() const { return DurationMs(rto_); }
// Returns the smoothed RTT value, in milliseconds.
DurationMs srtt() const { return DurationMs(srtt_); }
DurationMs srtt() const { return DurationMs(scaled_srtt_ >> kRttShift); }
private:
// Note that all intermediate state calculation is done in the floating point
// domain, to maintain precision.
const double min_rto_;
const double max_rto_;
const double max_rtt_;
const int32_t min_rto_;
const int32_t max_rto_;
const int32_t max_rtt_;
// If this is the first measurement
bool first_measurement_ = true;
// Smoothed Round-Trip Time
double srtt_ = 0.0;
// Round-Trip Time Variation
double rttvar_ = 0.0;
// Smoothed Round-Trip Time, shifted by kRttShift
int32_t scaled_srtt_ = 0;
// Round-Trip Time Variation, shifted by kRttVarShift
int32_t scaled_rtt_var_ = 0;
// Retransmission Timeout
double rto_;
int32_t rto_;
};
} // namespace dcsctp

View file

@ -88,7 +88,7 @@ TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) {
rto_.ObserveRTT(DurationMs(125));
EXPECT_EQ(*rto_.rto(), 233);
rto_.ObserveRTT(DurationMs(127));
EXPECT_EQ(*rto_.rto(), 208);
EXPECT_EQ(*rto_.rto(), 209);
}
TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) {
@ -116,21 +116,21 @@ TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) {
rto_.ObserveRTT(DurationMs(124));
EXPECT_EQ(*rto_.rto(), 800);
rto_.ObserveRTT(DurationMs(122));
EXPECT_EQ(*rto_.rto(), 709);
EXPECT_EQ(*rto_.rto(), 710);
rto_.ObserveRTT(DurationMs(123));
EXPECT_EQ(*rto_.rto(), 630);
EXPECT_EQ(*rto_.rto(), 631);
rto_.ObserveRTT(DurationMs(124));
EXPECT_EQ(*rto_.rto(), 561);
EXPECT_EQ(*rto_.rto(), 562);
rto_.ObserveRTT(DurationMs(122));
EXPECT_EQ(*rto_.rto(), 504);
EXPECT_EQ(*rto_.rto(), 505);
rto_.ObserveRTT(DurationMs(124));
EXPECT_EQ(*rto_.rto(), 453);
EXPECT_EQ(*rto_.rto(), 454);
rto_.ObserveRTT(DurationMs(124));
EXPECT_EQ(*rto_.rto(), 409);
EXPECT_EQ(*rto_.rto(), 410);
rto_.ObserveRTT(DurationMs(124));
EXPECT_EQ(*rto_.rto(), 372);
rto_.ObserveRTT(DurationMs(124));
EXPECT_EQ(*rto_.rto(), 339);
EXPECT_EQ(*rto_.rto(), 340);
}
TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) {