diff --git a/CMakeLists.txt b/CMakeLists.txt index 763db96..9818052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ add_library(solanaceae_ngcft1 ./solanaceae/ngc_ft1/cca.hpp ./solanaceae/ngc_ft1/ledbat.hpp ./solanaceae/ngc_ft1/ledbat.cpp + ./solanaceae/ngc_ft1/cubic.hpp + ./solanaceae/ngc_ft1/cubic.cpp ./solanaceae/ngc_ft1/rcv_buf.hpp ./solanaceae/ngc_ft1/rcv_buf.cpp diff --git a/solanaceae/ngc_ft1/cca.hpp b/solanaceae/ngc_ft1/cca.hpp index 37d8509..d10635d 100644 --- a/solanaceae/ngc_ft1/cca.hpp +++ b/solanaceae/ngc_ft1/cca.hpp @@ -3,6 +3,16 @@ #include #include +// TODO: refactor, more state tracking in ccai and seperate into flow and congestion algos +inline bool isSkipSeqID(const std::pair& a, const std::pair& b) { + // this is not perfect, would need more ft id based history + if (a.first != b.first) { + return false; // we dont know + } else { + return a.second+1 != b.second; + } +} + struct CCAI { public: // config using SeqIDType = std::pair; // tf_id, seq_id diff --git a/solanaceae/ngc_ft1/cubic.cpp b/solanaceae/ngc_ft1/cubic.cpp new file mode 100644 index 0000000..b705c5f --- /dev/null +++ b/solanaceae/ngc_ft1/cubic.cpp @@ -0,0 +1,49 @@ +#include "./cubic.hpp" + +#include +#include + +float CUBIC::getCWnD(void) const { + const double K = cbrt( + (_window_max * (1. - BETA)) / SCALING_CONSTANT + ); + + const double TK = _time_since_reduction - K; + + const double cwnd = + SCALING_CONSTANT + * TK * TK * TK // TK^3 + + _window_max + ; + + std::cout << "K:" << K << " TK:" << TK << " cwnd:" << cwnd << " rtt:" << getCurrentDelay() << "\n"; + + return cwnd; +} + +float CUBIC::getCurrentDelay(void) const { + return _rtt_ema; +} + +void CUBIC::addRTT(float new_delay) { + // lerp(new_delay, rtt_ema, 0.1) + _rtt_ema = RTT_EMA_ALPHA * new_delay + (1.f - RTT_EMA_ALPHA) * _rtt_ema; +} + +size_t CUBIC::canSend(void) const { + return 0; +} + +std::vector CUBIC::getTimeouts(void) const { + return {}; +} + +void CUBIC::onSent(SeqIDType seq, size_t data_size) { +} + +void CUBIC::onAck(std::vector seqs) { +} + +void CUBIC::onLoss(SeqIDType seq, bool discard) { +} + diff --git a/solanaceae/ngc_ft1/cubic.hpp b/solanaceae/ngc_ft1/cubic.hpp new file mode 100644 index 0000000..953b8b4 --- /dev/null +++ b/solanaceae/ngc_ft1/cubic.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "./cca.hpp" + +#include + +struct CUBIC : public CCAI { + using clock = std::chrono::steady_clock; + + public: // config + static constexpr float BETA {0.7f}; + static constexpr float SCALING_CONSTANT {0.4f}; + static constexpr float RTT_EMA_ALPHA = 0.1f; // 0.1 is very smooth, might need more + + private: + // window size before last reduciton + double _window_max {2.f * MAXIMUM_SEGMENT_SIZE}; // start with mss*2 + double _window_last_max {2.f * MAXIMUM_SEGMENT_SIZE}; + double _time_since_reduction {0.}; + + // initialize to low value, will get corrected very fast + float _fwnd {0.01f * max_byterate_allowed}; // in bytes + + // rtt exponental moving average + float _rtt_ema {0.5f}; + + clock::time_point _time_start_offset; + + private: + float getCWnD(void) const; + + // make values relative to algo start for readability (and precision) + // get timestamp in seconds + float getTimeNow(void) const { + return std::chrono::duration{clock::now() - _time_start_offset}.count(); + } + + // moving avg over the last few delay samples + // VERY sensitive to bundling acks + float getCurrentDelay(void) const; + + void addRTT(float new_delay); + + public: // api + CUBIC(size_t maximum_segment_data_size) : CCAI(maximum_segment_data_size) {} + + // TODO: api for how much data we should send + // take time since last sent into account + // respect max_byterate_allowed + size_t canSend(void) const override; + + // get the list of timed out seq_ids + std::vector getTimeouts(void) const override; + + public: // callbacks + // data size is without overhead + void onSent(SeqIDType seq, size_t data_size) override; + + void onAck(std::vector seqs) override; + + // if discard, not resent, not inflight + void onLoss(SeqIDType seq, bool discard) override; +}; + diff --git a/solanaceae/ngc_ft1/ledbat.cpp b/solanaceae/ngc_ft1/ledbat.cpp index c20a511..52e6265 100644 --- a/solanaceae/ngc_ft1/ledbat.cpp +++ b/solanaceae/ngc_ft1/ledbat.cpp @@ -1,4 +1,5 @@ #include "./ledbat.hpp" +#include "solanaceae/ngc_ft1/cca.hpp" #include #include @@ -68,6 +69,11 @@ void LEDBAT::onSent(SeqIDType seq, size_t data_size) { } void LEDBAT::onAck(std::vector seqs) { + if (seqs.empty()) { + assert(false && "got empty list of acks???"); + return; + } + // only take the smallest value float most_recent {-std::numeric_limits::infinity()}; @@ -75,6 +81,23 @@ void LEDBAT::onAck(std::vector seqs) { const auto now {getTimeNow()}; + { // skip in ack is congestion event + // 1. look at primary ack of packet + auto it = std::find_if(_in_flight.begin(), _in_flight.end(), [seq = seqs.front()](const auto& v) -> bool { + return std::get<0>(v) == seq; + }); + if (it != _in_flight.end()) { + if (isSkipSeqID(_last_ack_got, std::get<0>(*it))) { + if (getTimeNow() >= _last_congestion_event + _last_congestion_rtt) { + _recently_lost_data = true; + _last_congestion_event = getTimeNow(); + _last_congestion_rtt = getCurrentDelay(); + } + } + // TODO: only if newer, triggers double event otherwise (without a timer) + _last_ack_got = std::get<0>(*it); + } + } for (const auto& seq : seqs) { auto it = std::find_if(_in_flight.begin(), _in_flight.end(), [seq](const auto& v) -> bool { return std::get<0>(v) == seq; @@ -124,15 +147,17 @@ void LEDBAT::onLoss(SeqIDType seq, bool discard) { } // TODO: reset timestamp? +#if 0 // temporarily disable ce for timeout // at most once per rtt? // TODO: use delay at event instead if (getTimeNow() >= _last_congestion_event + _last_congestion_rtt) { _recently_lost_data = true; _last_congestion_event = getTimeNow(); _last_congestion_rtt = getCurrentDelay(); - - updateWindows(); } +#endif + + updateWindows(); } float LEDBAT::getCurrentDelay(void) const { @@ -205,7 +230,7 @@ void LEDBAT::updateWindows(void) { if (_recently_lost_data) { _cwnd = std::clamp( - _cwnd / 2.f, + _cwnd * 0.7f, //_cwnd / 1.6f, 2.f * MAXIMUM_SEGMENT_SIZE, _cwnd @@ -213,7 +238,7 @@ void LEDBAT::updateWindows(void) { } else { // LEDBAT++ (the Rethinking the LEDBAT Protocol paper) // "Multiplicative decrease" - const float constant {2.f}; // spec recs 1 + const float constant {1.f}; // spec recs 1 if (queuing_delay < target_delay) { _cwnd = std::min( _cwnd + gain, diff --git a/solanaceae/ngc_ft1/ledbat.hpp b/solanaceae/ngc_ft1/ledbat.hpp index b023f92..6f0def5 100644 --- a/solanaceae/ngc_ft1/ledbat.hpp +++ b/solanaceae/ngc_ft1/ledbat.hpp @@ -123,6 +123,8 @@ struct LEDBAT : public CCAI{ int64_t _in_flight_bytes {0}; + SeqIDType _last_ack_got {0xff, 0xffff}; // some default + private: // helper clock::time_point _time_start_offset; }; diff --git a/solanaceae/ngc_ft1_sha1/sha1_ngcft1.cpp b/solanaceae/ngc_ft1_sha1/sha1_ngcft1.cpp index 2ba189a..4f435c0 100644 --- a/solanaceae/ngc_ft1_sha1/sha1_ngcft1.cpp +++ b/solanaceae/ngc_ft1_sha1/sha1_ngcft1.cpp @@ -826,6 +826,7 @@ bool SHA1_NGCFT1::onEvent(const Events::NGCFT1_send_data& e) { } auto& transfer = peer.at(e.transfer_id); + transfer.time_since_activity = 0.f; if (std::holds_alternative(transfer.v)) { auto& info_transfer = std::get(transfer.v); for (size_t i = 0; i < e.data_size && (i + e.data_offset) < info_transfer.info_data.size(); i++) { @@ -859,8 +860,6 @@ bool SHA1_NGCFT1::onEvent(const Events::NGCFT1_send_data& e) { assert(false && "not implemented?"); } - transfer.time_since_activity = 0.f; - return true; }