Skip to content

Commit c1c0ad6

Browse files
refactor(s2n-quic-core): represent Bandwidth as nanos per byte (#1439)
* refactor(s2n-quic-core): represent Bandwidth as nanos per byte * Add comment * Add comment on max value
1 parent dcb8943 commit c1c0ad6

File tree

5 files changed

+82
-61
lines changed

5 files changed

+82
-61
lines changed

quic/s2n-quic-core/src/recovery/bandwidth/estimator.rs

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use crate::time::Timestamp;
5-
use core::{cmp::max, time::Duration};
5+
use core::{
6+
cmp::{max, Ordering},
7+
time::Duration,
8+
};
69
use num_rational::Ratio;
10+
use num_traits::Inv;
711

812
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
913
/// Bandwidth-related data tracked for each sent packet
@@ -26,45 +30,73 @@ pub struct PacketInfo {
2630
pub is_app_limited: bool,
2731
}
2832

29-
const MICRO_BITS_PER_BYTE: u64 = 8 * 1000000;
30-
31-
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
33+
/// Represents a rate at which data is transferred
34+
///
35+
/// While bandwidth is typically thought of as an amount of data over a fixed
36+
/// amount of time (bytes per second, for example), in this case we internally
37+
/// represent bandwidth as the inverse: an amount of time to send a fixed amount
38+
/// of data (nanoseconds per byte, in this case). This allows for some of the
39+
/// math operations needed on `Bandwidth` to avoid division, while reducing the
40+
/// likelihood of panicking due to overflow.
41+
///
42+
/// The maximum (non-infinite) value that can be represented is 1 GB/second.
43+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3244
pub struct Bandwidth {
33-
bits_per_second: u64,
45+
nanos_per_byte: u64,
3446
}
3547

3648
impl Bandwidth {
37-
pub const ZERO: Bandwidth = Bandwidth { bits_per_second: 0 };
38-
39-
pub const MAX: Bandwidth = Bandwidth {
40-
bits_per_second: u64::MAX,
49+
pub const ZERO: Bandwidth = Bandwidth {
50+
nanos_per_byte: u64::MAX,
4151
};
4252

53+
pub const INFINITY: Bandwidth = Bandwidth { nanos_per_byte: 0 };
54+
4355
/// Constructs a new `Bandwidth` with the given bytes per interval
4456
pub const fn new(bytes: u64, interval: Duration) -> Self {
45-
if interval.is_zero() {
57+
if interval.is_zero() || bytes == 0 {
4658
Bandwidth::ZERO
4759
} else {
4860
Self {
49-
// Prefer multiplying by MICRO_BITS_PER_BYTE first to avoid losing resolution
50-
bits_per_second: match bytes.checked_mul(MICRO_BITS_PER_BYTE) {
51-
Some(micro_bits) => micro_bits / interval.as_micros() as u64,
52-
None => {
53-
// If that overflows, divide first by the interval
54-
(bytes / interval.as_micros() as u64).saturating_mul(MICRO_BITS_PER_BYTE)
55-
}
56-
},
61+
nanos_per_byte: interval.as_nanos() as u64 / bytes,
5762
}
5863
}
5964
}
6065
}
6166

67+
impl Default for Bandwidth {
68+
fn default() -> Self {
69+
Bandwidth::ZERO
70+
}
71+
}
72+
73+
impl core::cmp::PartialOrd for Bandwidth {
74+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
75+
Some(self.cmp(other))
76+
}
77+
}
78+
79+
impl core::cmp::Ord for Bandwidth {
80+
fn cmp(&self, other: &Self) -> Ordering {
81+
// The higher the nano_per_byte, the lower the bandwidth,
82+
// so reverse the ordering when comparing
83+
self.nanos_per_byte.cmp(&other.nanos_per_byte).reverse()
84+
}
85+
}
86+
6287
impl core::ops::Mul<Ratio<u64>> for Bandwidth {
6388
type Output = Bandwidth;
6489

6590
fn mul(self, rhs: Ratio<u64>) -> Self::Output {
91+
if self == Bandwidth::ZERO {
92+
return Bandwidth::ZERO;
93+
}
94+
6695
Bandwidth {
67-
bits_per_second: (rhs * self.bits_per_second).to_integer(),
96+
// Since `Bandwidth` is represented as time/byte and not bytes/time, we should divide
97+
// by the given ratio to result in a higher nanos_per_byte value (lower bandwidth).
98+
// To avoid division, we can multiply by the inverse of the ratio instead
99+
nanos_per_byte: (rhs.inv() * self.nanos_per_byte).to_integer(),
68100
}
69101
}
70102
}
@@ -73,14 +105,10 @@ impl core::ops::Mul<Duration> for Bandwidth {
73105
type Output = u64;
74106

75107
fn mul(self, rhs: Duration) -> Self::Output {
76-
// Prefer multiplying by the duration first to avoid losing resolution
77-
match self.bits_per_second.checked_mul(rhs.as_micros() as u64) {
78-
Some(micro_bits) => micro_bits / MICRO_BITS_PER_BYTE,
79-
None => {
80-
// If that overflows, divide first by MICRO_BITS_PER_BYTE
81-
(self.bits_per_second / MICRO_BITS_PER_BYTE).saturating_mul(rhs.as_micros() as u64)
82-
}
108+
if self == Bandwidth::INFINITY {
109+
return u64::MAX;
83110
}
111+
rhs.as_nanos() as u64 / self.nanos_per_byte
84112
}
85113
}
86114

@@ -94,7 +122,7 @@ impl core::ops::Div<Bandwidth> for u64 {
94122
type Output = Duration;
95123

96124
fn div(self, rhs: Bandwidth) -> Self::Output {
97-
Duration::from_micros(self * MICRO_BITS_PER_BYTE / rhs.bits_per_second)
125+
Duration::from_nanos(rhs.nanos_per_byte.saturating_mul(self))
98126
}
99127
}
100128

quic/s2n-quic-core/src/recovery/bandwidth/estimator/tests.rs

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,15 @@ use crate::time::{Clock, NoopClock};
88
fn bandwidth() {
99
let result = Bandwidth::new(1000, Duration::from_secs(10));
1010

11-
assert_eq!(100 * 8, result.bits_per_second);
11+
// 10 seconds = 10^10 nanoseconds, nanos_per_byte = 10^10 / 1000 = 10_000_000
12+
assert_eq!(10_000_000, result.nanos_per_byte);
1213
}
1314

1415
#[test]
1516
fn bandwidth_saturating() {
1617
let result = Bandwidth::new(u64::MAX, Duration::from_secs(1));
1718

18-
assert_eq!(Bandwidth::MAX, result);
19-
}
20-
21-
#[test]
22-
fn bandwidth_overflow() {
23-
let result = Bandwidth::new(u64::MAX, Duration::from_secs(8));
24-
25-
let expected_bits_per_second =
26-
u64::MAX / (Duration::from_secs(8).as_micros() as u64) * MICRO_BITS_PER_BYTE;
27-
28-
assert_eq!(expected_bits_per_second, result.bits_per_second);
19+
assert_eq!(Bandwidth::INFINITY, result);
2920
}
3021

3122
#[test]
@@ -44,6 +35,11 @@ fn bandwidth_mul_ratio() {
4435
assert_eq!(result, Bandwidth::new(3000, Duration::from_secs(1)));
4536
}
4637

38+
#[test]
39+
fn bandwidth_zero_mul_ratio() {
40+
assert_eq!(Bandwidth::ZERO, Bandwidth::ZERO * Ratio::new(3, 7));
41+
}
42+
4743
#[test]
4844
fn bandwidth_mul_duration() {
4945
let bandwidth = Bandwidth::new(7000, Duration::from_secs(2));
@@ -55,25 +51,13 @@ fn bandwidth_mul_duration() {
5551

5652
#[test]
5753
fn bandwidth_mul_saturation() {
58-
let bandwidth = Bandwidth::MAX;
54+
let bandwidth = Bandwidth::INFINITY;
5955

6056
let result = bandwidth * Duration::from_secs(10);
6157

6258
assert_eq!(result, u64::MAX);
6359
}
6460

65-
#[test]
66-
fn bandwidth_mul_overflow() {
67-
let bandwidth = Bandwidth {
68-
bits_per_second: 18446744073704000000, // closest value to u64::MAX that divides evenly by MICRO_BITS_PER_BYTE
69-
};
70-
71-
let result = bandwidth * Duration::from_millis(500);
72-
73-
// Divide by 8 to convert to bytes and divide by 2 since we multiplied by .5 seconds
74-
assert_eq!(result, 18446744073704000000 / 8 / 2);
75-
}
76-
7761
#[test]
7862
fn u64_div_bandwidth() {
7963
let bandwidth = Bandwidth::new(10_000, Duration::from_secs(1));
@@ -85,6 +69,15 @@ fn u64_div_bandwidth() {
8569
assert_eq!(bytes / bandwidth, Duration::from_millis(200));
8670
}
8771

72+
#[test]
73+
fn bandwidth_ordering() {
74+
let low = Bandwidth::new(10_000, Duration::from_secs(1));
75+
let high = Bandwidth::new(20_000, Duration::from_secs(1));
76+
77+
assert!(high > low);
78+
assert_eq!(high, low.max(high));
79+
}
80+
8881
// first_sent_time and delivered_time typically hold values from recently acknowledged packets. However,
8982
// when no packet has been sent yet, or there are no packets currently in flight, these values are initialized
9083
// with the time when a packet is sent. This test confirms first_sent_time and delivered_time are

quic/s2n-quic-core/src/recovery/bbr/congestion.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ mod tests {
315315
loss_round_counter: Default::default(),
316316
loss_in_round: true,
317317
inflight_latest: 100,
318-
bw_latest: Bandwidth::MAX,
318+
bw_latest: Bandwidth::INFINITY,
319319
};
320320

321321
state.reset();

quic/s2n-quic-core/src/recovery/bbr/data_rate.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ impl Model {
4848

4949
Self {
5050
max_bw_filter: WindowedMaxFilter::new(MAX_BW_FILTER_LEN),
51-
bw_hi: Bandwidth::MAX,
52-
bw_lo: Bandwidth::MAX,
51+
bw_hi: Bandwidth::INFINITY,
52+
bw_lo: Bandwidth::INFINITY,
5353
bw: Bandwidth::ZERO,
5454
cycle_count: Default::default(),
5555
}
@@ -121,7 +121,7 @@ impl Model {
121121
//# BBRInitLowerBounds():
122122
//# if (BBR.bw_lo == Infinity)
123123
//# BBR.bw_lo = BBR.max_bw
124-
if self.bw_lo == Bandwidth::MAX {
124+
if self.bw_lo == Bandwidth::INFINITY {
125125
self.bw_lo = self.max_bw()
126126
}
127127

@@ -137,7 +137,7 @@ impl Model {
137137
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.3
138138
//# BBRResetLowerBounds():
139139
//# BBR.bw_lo = Infinity
140-
self.bw_lo = Bandwidth::MAX
140+
self.bw_lo = Bandwidth::INFINITY
141141
}
142142

143143
/// Bounds `bw` to min(`max_bw`, `bw_lo`, `bw_hi)
@@ -160,8 +160,8 @@ mod tests {
160160

161161
assert_eq!(Bandwidth::ZERO, model.max_bw());
162162
assert_eq!(Bandwidth::ZERO, model.bw());
163-
assert_eq!(Bandwidth::MAX, model.bw_hi());
164-
assert_eq!(Bandwidth::MAX, model.bw_lo());
163+
assert_eq!(Bandwidth::INFINITY, model.bw_hi());
164+
assert_eq!(Bandwidth::INFINITY, model.bw_lo());
165165
}
166166

167167
#[test]
@@ -246,7 +246,7 @@ mod tests {
246246

247247
// Resetting the lower bound sets bw_lo to Bandwidth::MAX
248248
model.reset_lower_bound();
249-
assert_eq!(Bandwidth::MAX, model.bw_lo());
249+
assert_eq!(Bandwidth::INFINITY, model.bw_lo());
250250
}
251251

252252
#[test]

quic/s2n-quic-core/src/recovery/bbr/probe_bw.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ mod tests {
905905
assert_eq!(CyclePhase::Refill, state.cycle_phase());
906906
// Lower bounds are reset
907907
assert_eq!(u64::MAX, data_volume_model.inflight_lo());
908-
assert_eq!(Bandwidth::MAX, data_rate_model.bw_lo());
908+
assert_eq!(Bandwidth::INFINITY, data_rate_model.bw_lo());
909909

910910
assert_eq!(0, state.bw_probe_up_rounds);
911911
assert_eq!(0, state.bw_probe_up_acks);

0 commit comments

Comments
 (0)