diff --git a/neqo-transport/src/cc/cubic.rs b/neqo-transport/src/cc/cubic.rs index aeae74c253..6a4630f58e 100644 --- a/neqo-transport/src/cc/cubic.rs +++ b/neqo-transport/src/cc/cubic.rs @@ -4,6 +4,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! CUBIC congestion control + use std::{ fmt::{self, Display}, time::{Duration, Instant}, @@ -13,19 +15,38 @@ use neqo_common::qtrace; use crate::cc::classic_cc::WindowAdjustment; -// CUBIC congestion control - -// C is a constant fixed to determine the aggressiveness of window -// increase in high BDP networks. +/// > C is a constant fixed to determine the aggressiveness of window +/// > increase in high BDP networks. +/// +/// +/// +/// See discussion for rational for concrete value. +/// +/// pub const CUBIC_C: f64 = 0.4; +/// TCP-friendly region additive factor +/// +/// pub const CUBIC_ALPHA: f64 = 3.0 * (1.0 - 0.7) / (1.0 + 0.7); -// CUBIC_BETA = 0.7; +/// `CUBIC_BETA` = 0.7; +/// +/// > Principle 4: To balance between the scalability and convergence speed, +/// > CUBIC sets the multiplicative window decrease factor to 0.7 while Standard +/// > TCP uses 0.5. While this improves the scalability of CUBIC, a side effect +/// > of this decision is slower convergence, especially under low statistical +/// > multiplexing environments. +/// +/// pub const CUBIC_BETA_USIZE_DIVIDEND: usize = 7; pub const CUBIC_BETA_USIZE_DIVISOR: usize = 10; -/// The fast convergence ratio further reduces the congestion window when a congestion event -/// occurs before reaching the previous `W_max`. +/// The fast convergence ratio further reduces the congestion window when a +/// congestion event occurs before reaching the previous `W_max`. +/// +/// See formula defined below. +/// +/// pub const CUBIC_FAST_CONVERGENCE: f64 = 0.85; // (1.0 + CUBIC_BETA) / 2.0; /// The minimum number of multiples of the datagram size that need @@ -47,11 +68,41 @@ pub fn convert_to_f64(v: usize) -> f64 { #[derive(Debug)] pub struct Cubic { + /// Maximum Window size two congestion events ago. + /// + /// > With fast convergence, when a congestion event occurs, before the + /// > window reduction of the congestion window, a flow remembers the last + /// > value of W_max before it updates W_max for the current congestion + /// > event. + /// + /// last_max_cwnd: f64, + /// Estimate of Standard TCP congestion window for Cubic's TCP-friendly + /// Region. + /// + /// > Standard TCP performs well in certain types of networks, for example, + /// > under short RTT and small bandwidth (or small BDP) networks. In + /// > these networks, we use the TCP-friendly region to ensure that CUBIC + /// > achieves at least the same throughput as Standard TCP. + /// + /// estimated_tcp_cwnd: f64, + /// > K is the time period that the above function takes to increase the + /// > current window size to W_max if there are no further congestion events + /// + /// k: f64, + /// > W_max is the window size just before the window is reduced in the last + /// > congestion event. + /// + /// w_max: f64, + /// > the elapsed time from the beginning of the current congestion + /// > avoidance + /// + /// ca_epoch_start: Option, + /// Number of bytes acked since the last Standard TCP congestion window increase. tcp_acked_bytes: f64, } @@ -91,12 +142,16 @@ impl Cubic { /// /// From that equation we can calculate K as: /// K = cubic_root((W_max - W_cubic) / C / MSS); + /// + /// fn calc_k(&self, curr_cwnd: f64, max_datagram_size: usize) -> f64 { ((self.w_max - curr_cwnd) / CUBIC_C / convert_to_f64(max_datagram_size)).cbrt() } /// W_cubic(t) = C*(t-K)^3 + W_max (Eq. 1) /// t is relative to the start of the congestion avoidance phase and it is in seconds. + /// + /// fn w_cubic(&self, t: f64, max_datagram_size: usize) -> f64 { (CUBIC_C * (t - self.k).powi(3)).mul_add(convert_to_f64(max_datagram_size), self.w_max) } @@ -144,6 +199,10 @@ impl WindowAdjustment for Cubic { self.tcp_acked_bytes += new_acked_f64; } + // Cubic concave or convex region + // + // + // let time_ca = self .ca_epoch_start .map_or(min_rtt, |t| { @@ -158,6 +217,9 @@ impl WindowAdjustment for Cubic { .as_secs_f64(); let target_cubic = self.w_cubic(time_ca, max_datagram_size); + // Cubic TCP-friendly region + // + // let max_datagram_size = convert_to_f64(max_datagram_size); let tcp_cnt = self.estimated_tcp_cwnd / CUBIC_ALPHA; let incr = (self.tcp_acked_bytes / tcp_cnt).floor(); @@ -166,10 +228,19 @@ impl WindowAdjustment for Cubic { self.estimated_tcp_cwnd += incr * max_datagram_size; } + // Take the larger cwnd of Cubic concave or convex and Cubic + // TCP-friendly region. + // + // > When receiving an ACK in congestion avoidance (cwnd could be + // > greater than or less than W_max), CUBIC checks whether W_cubic(t) is + // > less than W_est(t). If so, CUBIC is in the TCP-friendly region and + // > cwnd SHOULD be set to W_est(t) at each reception of an ACK. + // + // let target_cwnd = target_cubic.max(self.estimated_tcp_cwnd); // Calculate the number of bytes that would need to be acknowledged for an increase - // of `MAX_DATAGRAM_SIZE` to match the increase of `target - cwnd / cwnd` as defined + // of `max_datagram_size` to match the increase of `target - cwnd / cwnd` as defined // in the specification (Sections 4.4 and 4.5). // The amount of data required therefore reduces asymptotically as the target increases. // If the target is not significantly higher than the congestion window, require a very @@ -191,10 +262,13 @@ impl WindowAdjustment for Cubic { ) -> (usize, usize) { let curr_cwnd_f64 = convert_to_f64(curr_cwnd); // Fast Convergence + // // If congestion event occurs before the maximum congestion window before the last // congestion event, we reduce the the maximum congestion window and thereby W_max. // check cwnd + MAX_DATAGRAM_SIZE instead of cwnd because with cwnd in bytes, cwnd may be // slightly off. + // + // self.last_max_cwnd = if curr_cwnd_f64 + convert_to_f64(max_datagram_size) < self.last_max_cwnd { curr_cwnd_f64 * CUBIC_FAST_CONVERGENCE