Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(transport): auto-tune stream receive window #1868

Open
wants to merge 76 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
c0a72ce
test(transport): maximum throughput on unlimited bandwidth and 50ms
mxinden Apr 25, 2024
5c2aa4c
feat: auto-tune stream receive window
mxinden May 1, 2024
1bfd2f5
clippy
mxinden May 7, 2024
167d93f
Merge branch 'main' of https://github.com/mozilla/neqo into auto-tuning
mxinden May 7, 2024
edc4035
fix tests
mxinden May 7, 2024
12390c8
Don't increase on STREAM_DATA_BLOCKED
mxinden May 14, 2024
0ad1b77
enforce STREAM_MAX_ACTIVE_LIMIT
mxinden May 14, 2024
8e6a5af
add TODO starting below 1MiB
mxinden May 14, 2024
716ba20
Add NonRandomDelay
mxinden May 15, 2024
3b2a52b
Reduce transfer amount and throughput expectation
mxinden May 15, 2024
dba8190
Merge branch 'main' of https://github.com/mozilla/neqo into auto-tuning
mxinden May 15, 2024
9b9524e
Merge branch 'main' of https://github.com/mozilla/neqo into auto-tuning
mxinden Nov 1, 2024
1b2a370
test(transport): assert maximum bandwidth on gbit link
mxinden Apr 25, 2024
ac5d024
debugging
mxinden Nov 19, 2024
22f1d7e
Merge branch 'main' of https://github.com/mozilla/neqo into auto-tuning
mxinden Dec 1, 2024
db3cfe5
deduplicate in fc.rs
mxinden Dec 1, 2024
62dc2ba
Google vs Thomson vs BDP
mxinden Dec 2, 2024
841d086
More testing
mxinden Dec 2, 2024
5ec58cb
Adjust bench
mxinden Dec 2, 2024
6dd3829
Move to benchmark
mxinden Dec 2, 2024
a6fc729
fix(sim): correct `Waiting` state comparison in `NodeHolder::ready()`
mxinden Dec 2, 2024
f9b9a27
Add TODO on sent rtt in receive auto-tuning
mxinden Dec 2, 2024
8aff641
update every rtt/4 and don't use floating point division
mxinden Dec 13, 2024
4a146e6
Settle on Thomson algorithm
mxinden Dec 13, 2024
5e7d7d6
More aggressive increase
mxinden Dec 17, 2024
7d19f66
Limit send buffer
mxinden Dec 17, 2024
6ad6cda
Merge branch 'main' of https://github.com/mozilla/neqo into auto-tuning
mxinden Dec 17, 2024
86f72ef
restrict send buffer
mxinden Dec 17, 2024
673f7e6
Obsolete note
mxinden Dec 17, 2024
327d28b
Introduce MAX_SEND_BUFFER_SIZE
mxinden Dec 17, 2024
6bf1616
Renames
mxinden Dec 18, 2024
fd8d8bd
Fix unit tests
mxinden Dec 18, 2024
4ff2a0c
Write qlogs when QLOGDIR is set
mxinden Dec 18, 2024
432a83e
Add Mtu simulator Node
mxinden Dec 18, 2024
d26df37
cleanups
mxinden Dec 19, 2024
09be7df
Remove INITIAL_SEND_BUFFER_SIZE
mxinden Dec 19, 2024
13f848d
Fix import
mxinden Dec 19, 2024
42324d7
Cleanup bench
mxinden Dec 19, 2024
b8f742a
Move bench
mxinden Dec 19, 2024
78add98
Checkin min_bandwidth
mxinden Dec 19, 2024
3e04c76
Add license header
mxinden Dec 19, 2024
daa8acc
Use MBIT
mxinden Dec 29, 2024
3f8679f
Update max_data test
mxinden Dec 29, 2024
719a957
rename fraction const
mxinden Dec 29, 2024
a871dba
document correlation with DEFAULT_ACK_RATIO
mxinden Dec 29, 2024
c66140a
document max_allowed_sent_at
mxinden Dec 29, 2024
21cda8b
remove bandwidth debug
mxinden Dec 29, 2024
afa9a5e
Document auto-tuning algorithm
mxinden Dec 29, 2024
7b423b9
Rename Delay to RandomDelay
mxinden Dec 29, 2024
bf478c1
Remove RX_STREAM_DATA_WINDOW
mxinden Dec 29, 2024
43e4860
Remove wrong comment
mxinden Dec 29, 2024
569c5fa
Remove debugging assert
mxinden Dec 29, 2024
7ad539d
Remove wrong comment
mxinden Dec 29, 2024
a5df1d8
Update *Delay Debug impl
mxinden Dec 29, 2024
e5ba77b
Remove comment
mxinden Dec 29, 2024
1e3dd80
Outdated comment
mxinden Dec 29, 2024
2934ffa
Document Mtu node impl
mxinden Dec 29, 2024
8e0c6d8
Refactor fc_state_recv_7
mxinden Dec 31, 2024
4efa2f2
Remove todo to shrink buffer
mxinden Dec 31, 2024
e68813e
Remove outdated todo
mxinden Dec 31, 2024
e808b51
Add max_send_buffer_size test
mxinden Dec 31, 2024
57c2870
Merge remote-tracking branch 'mozilla/main' into auto-tuning
mxinden Dec 31, 2024
ba2c321
Clippy
mxinden Dec 31, 2024
6de63fb
Clippy
mxinden Dec 31, 2024
aba14e9
Plan tests
mxinden Jan 2, 2025
4ee45fc
Add tests
mxinden Jan 3, 2025
82eb50b
Fix min_bandwidth
mxinden Jan 3, 2025
fb51122
Add quick check style test
mxinden Jan 3, 2025
1fa5a77
Interleaving test
mxinden Jan 4, 2025
6cb8de9
Rational for MAX_RECV_WINDOW_SIZE
mxinden Jan 4, 2025
f10cf32
Clippy
mxinden Jan 4, 2025
8419c3f
Fix test
mxinden Jan 4, 2025
5724444
Fix intra doc links
mxinden Jan 4, 2025
4d70b34
Expose raw bandwidth
mxinden Jan 4, 2025
08c2d37
Use LINK_BANDWIDTH const
mxinden Jan 15, 2025
68291d8
Merge branch 'main' of https://github.com/mozilla/neqo into auto-tuning
mxinden Jan 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions neqo-http3/src/connection_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,7 +1293,7 @@ mod tests {
use neqo_qpack::{encoder::QPackEncoder, QpackSettings};
use neqo_transport::{
CloseReason, ConnectionEvent, ConnectionParameters, Output, State, StreamId, StreamType,
Version, MIN_INITIAL_PACKET_SIZE, RECV_BUFFER_SIZE, SEND_BUFFER_SIZE,
Version, INITIAL_RECV_WINDOW_SIZE, MIN_INITIAL_PACKET_SIZE,
};
use test_fixture::{
anti_replay, default_server_h3, fixture_init, new_server, now,
Expand Down Expand Up @@ -2742,7 +2742,7 @@ mod tests {
if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
if stream_id == request_stream_id {
// Read the DATA frame.
let mut buf = vec![1_u8; RECV_BUFFER_SIZE];
let mut buf = vec![1_u8; INITIAL_RECV_WINDOW_SIZE];
let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap();
assert!(fin);
assert_eq!(
Expand Down Expand Up @@ -2815,7 +2815,7 @@ mod tests {
assert_eq!(sent, Ok(first_frame.len()));

// The second frame cannot fit.
let sent = client.send_data(request_stream_id, &vec![0_u8; SEND_BUFFER_SIZE]);
let sent = client.send_data(request_stream_id, &vec![0_u8; INITIAL_RECV_WINDOW_SIZE]);
assert_eq!(sent, Ok(expected_second_data_frame.len()));

// Close stream.
Expand All @@ -2824,7 +2824,7 @@ mod tests {
let mut out = client.process_output(now());
// We need to loop a bit until all data has been sent. Once for every 1K
// of data.
for _i in 0..SEND_BUFFER_SIZE / 1000 {
for _i in 0..INITIAL_RECV_WINDOW_SIZE / 1000 {
out = server.conn.process(out.dgram(), now());
out = client.process(out.dgram(), now());
}
Expand All @@ -2834,7 +2834,7 @@ mod tests {
if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
if stream_id == request_stream_id {
// Read DATA frames.
let mut buf = vec![1_u8; RECV_BUFFER_SIZE];
let mut buf = vec![1_u8; INITIAL_RECV_WINDOW_SIZE];
let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap();
assert!(fin);
assert_eq!(
Expand Down Expand Up @@ -2887,7 +2887,7 @@ mod tests {
// After the first frame there is exactly 63+2 bytes left in the send buffer.
#[test]
fn fetch_two_data_frame_second_63bytes() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 88);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 88);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]);
}

Expand All @@ -2896,7 +2896,7 @@ mod tests {
// but we can only send 63 bytes.
#[test]
fn fetch_two_data_frame_second_63bytes_place_for_66() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 89);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 89);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]);
}

Expand All @@ -2905,15 +2905,15 @@ mod tests {
// but we can only send 64 bytes.
#[test]
fn fetch_two_data_frame_second_64bytes_place_for_67() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 90);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 90);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x40, 0x40], &[0_u8; 64]);
}

// Send 2 frames. For the second one we can only send 16383 bytes.
// After the first frame there is exactly 16383+3 bytes left in the send buffer.
#[test]
fn fetch_two_data_frame_second_16383bytes() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16409);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16409);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]);
}

Expand All @@ -2922,7 +2922,7 @@ mod tests {
// send 16383 bytes.
#[test]
fn fetch_two_data_frame_second_16383bytes_place_for_16387() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16410);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16410);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]);
}

Expand All @@ -2931,7 +2931,7 @@ mod tests {
// send 16383 bytes.
#[test]
fn fetch_two_data_frame_second_16383bytes_place_for_16388() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16411);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16411);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]);
}

Expand All @@ -2940,7 +2940,7 @@ mod tests {
// 16384 bytes.
#[test]
fn fetch_two_data_frame_second_16384bytes_place_for_16389() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16412);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16412);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x80, 0x0, 0x40, 0x0], &[0_u8; 16384]);
}

Expand Down
5 changes: 5 additions & 0 deletions neqo-transport/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,8 @@ required-features = ["bench"]
name = "sent_packets"
harness = false
required-features = ["bench"]

[[bench]]
name = "min_bandwidth"
harness = false
required-features = ["bench"]
78 changes: 78 additions & 0 deletions neqo-transport/benches/min_bandwidth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! A simulated transfer benchmark, asserting a minimum bandwidth.
//!
//! This is using [`test_fixture::sim`], i.e. does no I/O beyond the process
//! boundary and runs in simulated time. Given that [`test_fixture::sim`] is
//! deterministic, there is no need for multiple benchmark iterations. Still it
//! is a Rust benchmark instead of a unit test due to its runtime (> 10s) even
//! in Rust release mode.

use std::time::Duration;

use neqo_transport::{ConnectionParameters, State};
use test_fixture::{
boxed,
sim::{
connection::{ConnectionNode, ReachState, ReceiveData, SendData},
network::{Delay, Mtu, TailDrop},
Simulator,
},
};

#[allow(clippy::cast_precision_loss)]
pub fn main() {
const MIB: usize = 1_024 * 1_024;
const GIB: usize = 1_024 * MIB;

const MBIT: usize = 1_000 * 1_000;
const GBIT: usize = 1_000 * MBIT;

const TRANSFER_AMOUNT: usize = GIB;
const LINK_BANDWIDTH: usize = GBIT;
const LINK_RTT_MS: usize = 40;
const MINIMUM_EXPECTED_UTILIZATION: f64 = 0.5;

let gbit_link = || {
let rate_byte = GBIT / 8;
let capacity_byte = rate_byte as f64 * (LINK_RTT_MS as f64 / 1_000.0);
TailDrop::new(rate_byte, capacity_byte as usize, Duration::ZERO)
};

let simulated_time = Simulator::new(
"gbit-bandwidth",
boxed![
ConnectionNode::new_client(
ConnectionParameters::default(),
boxed![ReachState::new(State::Confirmed)],
boxed![ReceiveData::new(TRANSFER_AMOUNT)]
),
Mtu::new(1500),
gbit_link(),
Delay::new(Duration::from_millis(LINK_RTT_MS as u64 / 2)),
ConnectionNode::new_server(
ConnectionParameters::default(),
boxed![ReachState::new(State::Confirmed)],
boxed![SendData::new(TRANSFER_AMOUNT)]
),
Mtu::new(1500),
gbit_link(),
Delay::new(Duration::from_millis(LINK_RTT_MS as u64 / 2)),
],
)
.setup()
.run();

let achieved_bandwidth = TRANSFER_AMOUNT as f64 * 8.0 / simulated_time.as_secs_f64();

assert!(
LINK_BANDWIDTH as f64 * MINIMUM_EXPECTED_UTILIZATION < achieved_bandwidth,
"expected to reach {MINIMUM_EXPECTED_UTILIZATION} of maximum bandwidth ({} Mbit/s) but got {} Mbit/s",
LINK_BANDWIDTH / MBIT,
achieved_bandwidth / MBIT as f64,
);
}
6 changes: 3 additions & 3 deletions neqo-transport/benches/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use test_fixture::{
boxed,
sim::{
connection::{ConnectionNode, ReachState, ReceiveData, SendData},
network::{Delay, TailDrop},
network::{RandomDelay, TailDrop},
ReadySimulator, Simulator,
},
};
Expand All @@ -38,14 +38,14 @@ fn benchmark_transfer(c: &mut Criterion, label: &str, seed: Option<impl AsRef<st
boxed![SendData::new(TRANSFER_AMOUNT)]
),
TailDrop::dsl_uplink(),
Delay::new(ZERO..JITTER),
RandomDelay::new(ZERO..JITTER),
ConnectionNode::new_server(
ConnectionParameters::default().pmtud(true).pacing(pacing),
boxed![ReachState::new(State::Confirmed)],
boxed![ReceiveData::new(TRANSFER_AMOUNT)]
),
TailDrop::dsl_downlink(),
Delay::new(ZERO..JITTER),
RandomDelay::new(ZERO..JITTER),
];
let mut sim = Simulator::new(label, nodes);
if let Some(seed) = &seed {
Expand Down
22 changes: 17 additions & 5 deletions neqo-transport/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2125,7 +2125,13 @@ impl Connection {
&mut self,
builder: &mut PacketBuilder,
tokens: &mut Vec<RecoveryToken>,
now: Instant,
) {
let rtt = self.paths.primary().map_or_else(
|| RttEstimate::default().estimate(),
|p| p.borrow().rtt().estimate(),
);

let stats = &mut self.stats.borrow_mut();
let frame_stats = &mut stats.frame_tx;
if self.role == Role::Server {
Expand All @@ -2140,7 +2146,7 @@ impl Connection {
TransmissionPriority::Important,
] {
self.streams
.write_frames(prio, builder, tokens, frame_stats);
.write_frames(prio, builder, tokens, frame_stats, now, rtt);
if builder.is_full() {
return;
}
Expand All @@ -2159,7 +2165,7 @@ impl Connection {

for prio in [TransmissionPriority::High, TransmissionPriority::Normal] {
self.streams
.write_frames(prio, builder, tokens, &mut stats.frame_tx);
.write_frames(prio, builder, tokens, &mut stats.frame_tx, now, rtt);
if builder.is_full() {
return;
}
Expand Down Expand Up @@ -2189,8 +2195,14 @@ impl Connection {
return;
}

self.streams
.write_frames(TransmissionPriority::Low, builder, tokens, frame_stats);
self.streams.write_frames(
TransmissionPriority::Low,
builder,
tokens,
frame_stats,
now,
rtt,
);

#[cfg(test)]
if let Some(w) = &mut self.test_frame_writer {
Expand Down Expand Up @@ -2307,7 +2319,7 @@ impl Connection {
.send_probe(builder, &mut self.stats.borrow_mut());
ack_eliciting = true;
}
self.write_appdata_frames(builder, &mut tokens);
self.write_appdata_frames(builder, &mut tokens, now);
} else {
let stats = &mut self.stats.borrow_mut().frame_tx;
self.crypto.write_frame(space, builder, &mut tokens, stats);
Expand Down
10 changes: 5 additions & 5 deletions neqo-transport/src/connection/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{cmp::max, time::Duration};
pub use crate::recovery::FAST_PTO_SCALE;
use crate::{
connection::{ConnectionIdManager, Role, LOCAL_ACTIVE_CID_LIMIT},
recv_stream::RECV_BUFFER_SIZE,
recv_stream::INITIAL_RECV_WINDOW_SIZE,
rtt::GRANULARITY,
stream_id::StreamType,
tparams::{self, PreferredAddress, TransportParameter, TransportParametersHandler},
Expand All @@ -25,7 +25,7 @@ const LOCAL_STREAM_LIMIT_UNI: u64 = 16;
pub const ACK_RATIO_SCALE: u8 = 10;
/// By default, aim to have the peer acknowledge 4 times per round trip time.
/// See `ConnectionParameters.ack_ratio` for more.
const DEFAULT_ACK_RATIO: u8 = 4 * ACK_RATIO_SCALE;
pub(crate) const DEFAULT_ACK_RATIO: u8 = 4 * ACK_RATIO_SCALE;
/// The local value for the idle timeout period.
const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(30);
const MAX_QUEUED_DATAGRAMS_DEFAULT: usize = 10;
Expand Down Expand Up @@ -91,9 +91,9 @@ impl Default for ConnectionParameters {
versions: VersionConfig::default(),
cc_algorithm: CongestionControlAlgorithm::NewReno,
max_data: LOCAL_MAX_DATA,
max_stream_data_bidi_remote: u64::try_from(RECV_BUFFER_SIZE).unwrap(),
max_stream_data_bidi_local: u64::try_from(RECV_BUFFER_SIZE).unwrap(),
max_stream_data_uni: u64::try_from(RECV_BUFFER_SIZE).unwrap(),
max_stream_data_bidi_remote: u64::try_from(INITIAL_RECV_WINDOW_SIZE).unwrap(),
max_stream_data_bidi_local: u64::try_from(INITIAL_RECV_WINDOW_SIZE).unwrap(),
max_stream_data_uni: u64::try_from(INITIAL_RECV_WINDOW_SIZE).unwrap(),
max_streams_bidi: LOCAL_STREAM_LIMIT_BIDI,
max_streams_uni: LOCAL_STREAM_LIMIT_UNI,
ack_ratio: DEFAULT_ACK_RATIO,
Expand Down
19 changes: 4 additions & 15 deletions neqo-transport/src/connection/tests/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use crate::{
FRAME_TYPE_STREAM_CLIENT_BIDI, FRAME_TYPE_STREAM_DATA_BLOCKED,
},
packet::PacketBuilder,
recv_stream::RECV_BUFFER_SIZE,
send_stream::{OrderGroup, SendStreamState, SEND_BUFFER_SIZE},
recv_stream::INITIAL_RECV_WINDOW_SIZE,
send_stream::{OrderGroup, SendStreamState},
streams::{SendOrder, StreamOrder},
tparams::{self, TransportParameter},
CloseReason, Connection, ConnectionParameters, Error, StreamId, StreamType,
Expand Down Expand Up @@ -437,18 +437,7 @@ fn max_data() {
client.streams.handle_max_data(100_000_000);
assert_eq!(
client.stream_avail_send_space(stream_id).unwrap(),
SEND_BUFFER_SIZE - SMALL_MAX_DATA
);

// Increase max stream data. Avail space now limited by tx buffer
client
.streams
.get_send_stream_mut(stream_id)
.unwrap()
.set_max_stream_data(100_000_000);
assert_eq!(
client.stream_avail_send_space(stream_id).unwrap(),
SEND_BUFFER_SIZE - SMALL_MAX_DATA + 4096
INITIAL_RECV_WINDOW_SIZE - SMALL_MAX_DATA
);

let evts = client.events().collect::<Vec<_>>();
Expand Down Expand Up @@ -846,7 +835,7 @@ fn stream_data_blocked_generates_max_stream_data() {
}
written += amount;
}
assert_eq!(written, RECV_BUFFER_SIZE);
assert_eq!(written, INITIAL_RECV_WINDOW_SIZE);
}

/// See <https://github.com/mozilla/neqo/issues/871>
Expand Down
Loading
Loading