Skip to content

Commit

Permalink
Update SEARCH code to v3.0
Browse files Browse the repository at this point in the history
* Change to cumulative byte tracking
* Bin type configurable for per-flow memory conservation
* Add configurable missed-bin reset
  • Loading branch information
AmberCronin committed Nov 22, 2024
1 parent cfdd201 commit 5a6bce8
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 99 deletions.
7 changes: 5 additions & 2 deletions include/quicly/cc.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ extern "C" {
#define QUICLY_SEARCH_TOTAL_BIN_COUNT (25) // number of search sent bytes bins
#define QUICLY_SEARCH_WINDOW_MULTIPLIER (3.5) // search multiplier for window calculation
#define QUICLY_SEARCH_THRESH (0.35) // search threshold to exit slow start phase
#define QUICLY_SEARCH_BIN_TYPE uint16_t // datatype used for bin storage
#define QUICLY_SEARCH_BITSHIFT (1) // space-saving bitshift for smaller bins
#define QUICLY_SEARCH_RESET_LIMIT (3) // number of bins that can be missed before triggering a reset

/**
* Holds pointers to concrete congestion control implementation functions.
Expand Down Expand Up @@ -71,7 +74,7 @@ typedef struct st_quicly_cc_t {
/**
* Bins for the byte count sent and the byte count delivered (instantiated on init)
*/
uint64_t delv_bins[QUICLY_SEARCH_TOTAL_BIN_COUNT];
QUICLY_SEARCH_BIN_TYPE delv_bins[QUICLY_SEARCH_TOTAL_BIN_COUNT];
/**
* Maintains the end time of the current bin
*/
Expand All @@ -84,7 +87,7 @@ typedef struct st_quicly_cc_t {
* Counts the number of times that the bin has been incremented, so we know when to
* start trying to watch for congestion
*/
uint32_t bin_rounds;
uint16_t bin_rounds;
} search;
} ss_state;
/**
Expand Down
6 changes: 3 additions & 3 deletions lib/ss-rfc2001.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
void ss_quicly_default(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight,
uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size)
{
cc->cwnd += bytes;
if (cc->cwnd_maximum < cc->cwnd)
cc->cwnd_maximum = cc->cwnd;
cc->cwnd += bytes;
if (cc->cwnd_maximum < cc->cwnd)
cc->cwnd_maximum = cc->cwnd;
}

quicly_ss_type_t quicly_ss_type_rfc2001= { "rfc2001", ss_quicly_default };
Expand Down
190 changes: 96 additions & 94 deletions lib/ss-search.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,121 +36,123 @@
* Improving QUIC Slow Start Behavior in Wireless Networks with SEARCH, In Proceedings of the IEEE
* Local and Metropolitan Area Conference (LANMAN), Boston, MA, USA, July 2024.
*
* [2] Maryam Ataei Kachooei, Jae Chung, Feng Li, Benjamin Peters, Josh Chung, and
* Mark Claypool. Improving TCP Slow Start Performance in Wireless Networks with
* SEARCH, In Proceedings of the World of Wireless, Mobile and Multimedia Networks
* (WoWMoM), Perth, Australia June 2024.
* [2] Maryam Ataei Kachooei, Jae Chung, Feng Li, Benjamin Peters, Josh Chung, and
* Mark Claypool. Improving TCP Slow Start Performance in Wireless Networks with
* SEARCH, In Proceedings of the World of Wireless, Mobile and Multimedia Networks
* (WoWMoM), Perth, Australia June 2024.
*
*/
*/

// define this internally, it's a "private" function...
void ss_search_reset(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, int64_t now);

void ss_search_reset(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, int64_t now)
{
// Handy pointers to the cc struct
uint64_t* delv = cc->ss_state.search.delv_bins;
int64_t* bin_end = &cc->ss_state.search.bin_end;
uint32_t* bin_time = &cc->ss_state.search.bin_time;
uint32_t* bin_rounds = &cc->ss_state.search.bin_rounds;
// Handy pointers to the cc struct
QUICLY_SEARCH_BIN_TYPE* delv = cc->ss_state.search.delv_bins;
int64_t* bin_end = &cc->ss_state.search.bin_end;
uint32_t* bin_time = &cc->ss_state.search.bin_time;
uint16_t* bin_rounds = &cc->ss_state.search.bin_rounds;

// bin time is the size of each of the sent/delv bins
uint32_t tmp_bin_time = (loss->rtt.latest * QUICLY_SEARCH_WINDOW_MULTIPLIER) / (QUICLY_SEARCH_DELV_BIN_COUNT);
*bin_time = tmp_bin_time < 1 ? 1 : tmp_bin_time;
*bin_end = now + *bin_time;
delv[0] = 0;
*bin_rounds = 0;
// bin time is the size of each of the sent/delv bins
uint32_t tmp_bin_time = (loss->rtt.latest * QUICLY_SEARCH_WINDOW_MULTIPLIER) / (QUICLY_SEARCH_DELV_BIN_COUNT);
*bin_time = tmp_bin_time < 1 ? 1 : tmp_bin_time;
*bin_end = now + *bin_time;
delv[0] = bytes;
*bin_rounds = 0;
}

// bytes is the number of bytes acked in the last ACK frame
// inflight is sentmap->bytes_in_flight + bytes
void ss_search(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight,
uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size)
{
// Handy pointers to the cc struct
uint64_t* delv = cc->ss_state.search.delv_bins;
int64_t* bin_end = &cc->ss_state.search.bin_end;
uint32_t* bin_time = &cc->ss_state.search.bin_time;
uint32_t* bin_rounds = &cc->ss_state.search.bin_rounds;
// Handy pointers to the cc struct
QUICLY_SEARCH_BIN_TYPE* delv = cc->ss_state.search.delv_bins;
int64_t* bin_end = &cc->ss_state.search.bin_end;
uint32_t* bin_time = &cc->ss_state.search.bin_time;
uint16_t* bin_rounds = &cc->ss_state.search.bin_rounds;

// struct initializations, everything else important has already been reset to 0
if(*bin_time == 0) {
ss_search_reset(cc, loss, bytes, now);
}

// struct initializations, everything else important has already been reset to 0
if(*bin_time == 0) {
ss_search_reset(cc, loss, bytes, now);
}
// perform a reset if a certain number of bins were missed (application limited case).
// non-application-limited delay will trigger a protocol reset if in-flight packets
// are not acked (typical case, not handled by us).
if (((now - *bin_end) / *bin_time) >= QUICLY_SEARCH_RESET_LIMIT) {
ss_search_reset(cc, loss, bytes + delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))], now);
}

// bin_shift is the number of bins to shift backwards, based on the latest RTT
uint8_t bin_shift = loss->rtt.latest / *bin_time;
if(bin_shift == 0) {
bin_shift = 1;
}
else if(loss->rtt.latest % *bin_time > (*bin_time / 2)) {
// round to the nearest bin (not doing interpolation yet)
bin_shift++;
}
// bin_shift is the number of bins to shift backwards, based on the latest RTT
uint8_t bin_shift = loss->rtt.latest / *bin_time;
if(bin_shift == 0) {
// shift at least one bin
bin_shift = 1;
}
else if(loss->rtt.latest % *bin_time > (*bin_time / 2)) {
// round to the nearest bin (not doing interpolation yet)
bin_shift++;
}

// Possibly add some code here for dirty reset - run when no data has been sent on the connection
// for a very long time, but application never received a loss (and so is still in slow-start)
// This is likely handled by the prior binroll while loop, but that might add unnecessary latency
// dependant on how long ago the last packet was acknowledged.
if (((now - *bin_end) / *bin_time) > QUICLY_SEARCH_TOTAL_BIN_COUNT) {
ss_search_reset(cc, loss, bytes, now);
}

// perform prior binrolls before updating the latest bin to run SEARCH on if necessary
while((now - *bin_time) > (*bin_end)) {
// initialize the next bin with the final value of the previous
delv[((*bin_rounds + 1) % QUICLY_SEARCH_TOTAL_BIN_COUNT)] = delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))];
*bin_end += *bin_time;
*bin_rounds += 1;
}
// perform current binroll
if((now > (*bin_end))) {
// only perform SEARCH if there is enough data in the sent bins with the current RTT
// bin_rounds tracks how many times we've rolled over, and a single window is the entire
// delivered bin count (because of the definition of how bin_time is calculated)
// thus, the number of rounds must be >= than the delv bin count + the bin shift
if((*bin_rounds) >= ((QUICLY_SEARCH_DELV_BIN_COUNT) + bin_shift)
&& bin_shift < (QUICLY_SEARCH_TOTAL_BIN_COUNT - QUICLY_SEARCH_DELV_BIN_COUNT)) {
// do SEARCH
double shift_delv_sum = delv[(*bin_rounds - bin_shift) % (QUICLY_SEARCH_TOTAL_BIN_COUNT)] \
- delv[(*bin_rounds - bin_shift - QUICLY_SEARCH_DELV_BIN_COUNT) % (QUICLY_SEARCH_TOTAL_BIN_COUNT)];
double delv_sum = delv[(*bin_rounds) % (QUICLY_SEARCH_TOTAL_BIN_COUNT)] \
- delv[(*bin_rounds - QUICLY_SEARCH_DELV_BIN_COUNT) % (QUICLY_SEARCH_TOTAL_BIN_COUNT)];

// perform prior binrolls before updating the latest bin to run SEARCH on if necessary
while((now - *bin_time) > (*bin_end)) {
*bin_end += *bin_time;
*bin_rounds += 1;
delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] = 0;
}
// perform current binroll
if((now > (*bin_end))) {
// only perform SEARCH if there is enough data in the sent bins with the current RTT
// bin_rounds tracks how many times we've rolled over, and a single window is the entire
// delivered bin count (because of the definition of how bin_time is calculated)
// thus, the number of rounds must be >= than the delv bin count + the bin shift
if((*bin_rounds) >= ((QUICLY_SEARCH_DELV_BIN_COUNT) + bin_shift)
&& bin_shift < (QUICLY_SEARCH_TOTAL_BIN_COUNT - QUICLY_SEARCH_DELV_BIN_COUNT)) {
// do SEARCH
double shift_delv_sum = 0, delv_sum = 0;
for (int i = *bin_rounds; i > (*bin_rounds - (QUICLY_SEARCH_DELV_BIN_COUNT)); i--) {
// the value of bin_shift will always be at least 1, so the current sent bin is never used
shift_delv_sum += delv[((i - bin_shift) % (QUICLY_SEARCH_TOTAL_BIN_COUNT))];
delv_sum += delv[(i % (QUICLY_SEARCH_TOTAL_BIN_COUNT))];
}
if (shift_delv_sum >= 1) {
shift_delv_sum *= 2;
double normalized_diff = (shift_delv_sum - delv_sum) / shift_delv_sum;
if (normalized_diff > QUICLY_SEARCH_THRESH) {
// exit slow start
// TODO: Proposal to lower cwnd by tracked previously sent bytes
if (cc->cwnd_maximum < cc->cwnd)
cc->cwnd_maximum = cc->cwnd;
cc->ssthresh = cc->cwnd;
cc->cwnd_exiting_slow_start = cc->cwnd;
cc->exit_slow_start_at = now;
return;
}
}
}
else if(bin_shift >= (QUICLY_SEARCH_TOTAL_BIN_COUNT - QUICLY_SEARCH_DELV_BIN_COUNT)) {
/* TODO: Double bin_time and consolidate for high RTT operation */
}
if (shift_delv_sum >= 1) {
shift_delv_sum *= 2;
double normalized_diff = (shift_delv_sum - delv_sum) / shift_delv_sum;
if (normalized_diff > QUICLY_SEARCH_THRESH) {
// exit slow start
// TODO: Proposal to lower cwnd by tracked previously sent bytes
if (cc->cwnd_maximum < cc->cwnd)
cc->cwnd_maximum = cc->cwnd;
cc->ssthresh = cc->cwnd;
cc->cwnd_exiting_slow_start = cc->cwnd;
cc->exit_slow_start_at = now;
return;
}
}
}
else if(bin_shift >= (QUICLY_SEARCH_TOTAL_BIN_COUNT - QUICLY_SEARCH_DELV_BIN_COUNT)) {
/* TODO: Double bin_time and consolidate for high RTT operation */
}

*bin_end += *bin_time;
*bin_rounds += 1;
delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] = 0;
}
delv[((*bin_rounds + 1) % QUICLY_SEARCH_TOTAL_BIN_COUNT)] = delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))];
*bin_end += *bin_time;
*bin_rounds += 1;
}

// fill (updated) bin with latest acknowledged bytes
// TCP implementation has a method of tracking total delivered bytes to avoid this per-packet
// computation, but we aren't doing that (yet). loss->total_bytes_sent looks interesting, but
// does not seem to guarantee a match with conn->egress.max_data.sent (see loss.c)
delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] += bytes;
// fill latest bin with latest acknowledged bytes
// TCP implementation has a method of tracking total delivered bytes to avoid this per-packet
// computation, but we aren't doing that (yet). loss->total_bytes_sent looks interesting, but
// does not seem to guarantee a match with conn->egress.max_data.sent (see loss.c)
//
// we include a right-bitshift to reduce the amount of data being stored in the bin
delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] += (bytes >> QUICLY_SEARCH_BITSHIFT);

// perform standard SS doubling
cc->cwnd += bytes;
if (cc->cwnd_maximum < cc->cwnd)
cc->cwnd_maximum = cc->cwnd;
// perform standard SS doubling
cc->cwnd += bytes;
if (cc->cwnd_maximum < cc->cwnd)
cc->cwnd_maximum = cc->cwnd;
}

quicly_ss_type_t quicly_ss_type_search = { "search", ss_search };

0 comments on commit 5a6bce8

Please sign in to comment.