diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index e2a4300ce..e5bad7fbd 100644 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -554,13 +554,10 @@ namespace libtorrent return (m_endgame_mode || m_snubbed) ? 1 : m_desired_queue_size; } - bool bittyrant_unchoke_compare( - peer_connection const* p) const; // compares this connection against the given connection // for which one is more eligible for an unchoke. // returns true if this is more eligible - bool unchoke_compare(peer_connection const* p) const; - bool upload_rate_compare(peer_connection const* p) const; + int download_payload_rate() const { return m_statistics.download_payload_rate(); } // resets the byte counters that are used to measure @@ -750,6 +747,10 @@ namespace libtorrent size_type uploaded_since_unchoked() const { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + // the time we last unchoked this peer + ptime time_of_last_unchoke() const + { return m_last_unchoke; } + // called when the disk write buffer is drained again, and we can // start downloading payload again void on_disk(); @@ -769,6 +770,8 @@ namespace libtorrent counters& stats_counters() const { return m_counters; } + int get_priority(int channel) const; + protected: size_t try_read(sync_t s, error_code& ec); @@ -878,8 +881,6 @@ namespace libtorrent int wanted_transfer(int channel); int request_bandwidth(int channel, int bytes = 0); - int get_priority(int channel) const; - boost::shared_ptr m_socket; // the queue of blocks we have requested @@ -981,6 +982,7 @@ namespace libtorrent // the time we received the last // piece request from the peer ptime m_last_incoming_request; + // the time when we unchoked this peer ptime m_last_unchoke; diff --git a/src/choker.cpp b/src/choker.cpp index 82964023d..b4b0bc09c 100644 --- a/src/choker.cpp +++ b/src/choker.cpp @@ -33,11 +33,173 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/choker.hpp" #include "libtorrent/peer_connection.hpp" #include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/torrent.hpp" #include namespace libtorrent { + + // return true if 'lhs' peer should be preferred to be unchoke over 'rhs' + + // TODO: 3 split this funcion up into multiple functions, one for each + // seed_choking_algorithm, and pick which one to use when calling + // the sort function + bool unchoke_compare(peer_connection const* lhs + , peer_connection const* rhs, aux::session_settings const& sett) + { + // if one peer belongs to a higher priority torrent than the other one + // that one should be unchoked. + boost::shared_ptr t1 = lhs->associated_torrent().lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = rhs->associated_torrent().lock(); + TORRENT_ASSERT(t2); + + int prio1 = lhs->get_priority(peer_connection::upload_channel); + int prio2 = rhs->get_priority(peer_connection::upload_channel); + + if (prio1 != prio2) + return prio1 > prio2; + + // compare how many bytes they've sent us + size_type c1; + size_type c2; + c1 = lhs->downloaded_in_last_round(); + c2 = rhs->downloaded_in_last_round(); + + if (c1 != c2) return c1 > c2; + + if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::round_robin) + { + // the amount uploaded since unchoked (not just in the last round) + c1 = lhs->uploaded_since_unchoked(); + c2 = rhs->uploaded_since_unchoked(); + + // the way the round-robin unchoker works is that it, + // by default, prioritizes any peer that is already unchoked. + // this maintain the status quo across unchoke rounds. However, + // peers that are unchoked, but have sent more than one quota + // since they were unchoked, they get de-prioritized. + + int pieces = sett.get_int(settings_pack::seeding_piece_quota); + // if a peer is already unchoked, and the number of bytes sent since it was unchoked + // is greater than the send quanta, then it's done with it' upload slot, and we + // can de-prioritize it + bool c1_quota_complete = !lhs->is_choked() && c1 + > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); + bool c2_quota_complete = !rhs->is_choked() && c2 + > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); + + // if c2 has completed a quanta, it shuold be de-prioritized + // and vice versa + if (c1_quota_complete < c2_quota_complete) return true; + if (c1_quota_complete > c2_quota_complete) return false; + + // if both peers have either completed a quanta, or not. + // keep unchoked peers prioritized over choked ones, to let + // peers keep working on uploading a full quanta + if (lhs->is_choked() < rhs->is_choked()) return true; + if (lhs->is_choked() > rhs->is_choked()) return false; + + // if the peers are still identical (say, they're both waiting to be unchoked) + // fall through and rely on the logic to prioritize peers who have waited + // the longest to be unchoked + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::fastest_upload) + { + c1 = lhs->uploaded_in_last_round(); + c2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + c1 *= prio1; + c2 *= prio2; + + if (c1 > c2) return true; + if (c2 > c1) return false; + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::anti_leech) + { + // the anti-leech seeding algorithm is based on the paper "Improving + // BitTorrent: A Simple Approach" from Chow et. al. and ranks peers based + // on how many pieces they have, prefering to unchoke peers that just + // started and peers that are close to completing. Like this: + // ^ + // | \ / | + // | \ / | + // | \ / | + // s | \ / | + // c | \ / | + // o | \ / | + // r | \ / | + // e | \ / | + // | \ / | + // | \ / | + // | \ / | + // | \ / | + // | V | + // +---------------------------+ + // 0% num have pieces 100% + int t1_total = t1->torrent_file().num_pieces(); + int t2_total = t2->torrent_file().num_pieces(); + int score1 = (lhs->num_have_pieces() < t1_total / 2 + ? t1_total - lhs->num_have_pieces() : lhs->num_have_pieces()) * 1000 / t1_total; + int score2 = (rhs->num_have_pieces() < t2_total / 2 + ? t2_total - rhs->num_have_pieces() : rhs->num_have_pieces()) * 1000 / t2_total; + if (score1 > score2) return true; + if (score2 > score1) return false; + } + + // prioritize the one that has waited the longest to be unchoked + // the round-robin unchoker relies on this logic. Don't change it + // without moving this into that unchoker logic + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } + + bool upload_rate_compare(peer_connection const* lhs + , peer_connection const* rhs) + { + size_type c1; + size_type c2; + + c1 = lhs->uploaded_in_last_round(); + c2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + c1 *= lhs->get_priority(peer_connection::upload_channel); + c2 *= rhs->get_priority(peer_connection::upload_channel); + + return c1 > c2; + } + + bool bittyrant_unchoke_compare(peer_connection const* lhs + , peer_connection const* rhs) + { + size_type d1, d2, u1, u2; + + // first compare how many bytes they've sent us + d1 = lhs->downloaded_in_last_round(); + d2 = rhs->downloaded_in_last_round(); + // divided by the number of bytes we've sent them + u1 = lhs->uploaded_in_last_round(); + u2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + d1 *= lhs->get_priority(peer_connection::upload_channel); + d2 *= rhs->get_priority(peer_connection::upload_channel); + + d1 = d1 * 1000 / (std::max)(size_type(1), u1); + d2 = d2 * 1000 / (std::max)(size_type(1), u2); + if (d1 > d2) return true; + if (d1 < d2) return false; + + // if both peers are still in their send quota or not in their send quota + // prioritize the one that has waited the longest to be unchoked + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } + int unchoke_sort(std::vector& peers , int max_upload_rate , time_duration unchoke_interval @@ -78,11 +240,8 @@ namespace libtorrent // if we're using the bittyrant choker, sort peers by their return // on investment. i.e. download rate / upload rate - - // TODO: make the comparison function a free function and move it - // into this source file std::sort(peers.begin(), peers.end() - , boost::bind(&peer_connection::bittyrant_unchoke_compare, _1, _2)); + , boost::bind(&bittyrant_unchoke_compare, _1, _2)); int upload_capacity_left = max_upload_rate; @@ -129,7 +288,7 @@ namespace libtorrent // TODO: make the comparison function a free function and move it // into this cpp file std::sort(peers.begin(), peers.end() - , boost::bind(&peer_connection::upload_rate_compare, _1, _2)); + , boost::bind(&upload_rate_compare, _1, _2)); // TODO: make configurable int rate_threshold = 1024; @@ -151,17 +310,17 @@ namespace libtorrent ++upload_slots; } - // sorts the peers that are eligible for unchoke by download rate and secondary - // by total upload. The reason for this is, if all torrents are being seeded, - // the download rate will be 0, and the peers we have sent the least to should - // be unchoked + // sorts the peers that are eligible for unchoke by download rate and + // secondary by total upload. The reason for this is, if all torrents are + // being seeded, the download rate will be 0, and the peers we have sent + // the least to should be unchoked - // TODO: use partial_sort + // we use partial sort here, because we only care about the top + // upload_slots peers. - // TODO: make the comparison function a free function and move it into - // this cpp file - std::sort(peers.begin(), peers.end() - , boost::bind(&peer_connection::unchoke_compare, _1, _2)); + std::partial_sort(peers.begin(), peers.begin() + + (std::min)(upload_slots, int(peers.size())), peers.end() + , boost::bind(&unchoke_compare, _1, _2, boost::cref(sett))); return upload_slots; } diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 43f877080..8a84606b8 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -294,151 +294,6 @@ namespace libtorrent * m_settings.get_int(settings_pack::decrease_est_reciprocation_rate) / 100; } - bool peer_connection::bittyrant_unchoke_compare( - peer_connection const* p) const - { - TORRENT_ASSERT(is_single_thread()); - TORRENT_ASSERT(p); - peer_connection const& rhs = *p; - - size_type d1, d2, u1, u2; - - // first compare how many bytes they've sent us - d1 = downloaded_in_last_round(); - d2 = rhs.downloaded_in_last_round(); - // divided by the number of bytes we've sent them - u1 = uploaded_in_last_round(); - u2 = rhs.uploaded_in_last_round(); - - // take torrent priority into account - d1 *= get_priority(upload_channel); - d2 *= rhs.get_priority(upload_channel); - - d1 = d1 * 1000 / (std::max)(size_type(1), u1); - d2 = d2 * 1000 / (std::max)(size_type(1), u2); - if (d1 > d2) return true; - if (d1 < d2) return false; - - // if both peers are still in their send quota or not in their send quota - // prioritize the one that has waited the longest to be unchoked - return m_last_unchoke < rhs.m_last_unchoke; - } - - // return true if 'this' peer should be preferred to be unchoke over p - bool peer_connection::unchoke_compare(peer_connection const* p) const - { - TORRENT_ASSERT(is_single_thread()); - TORRENT_ASSERT(p); - peer_connection const& rhs = *p; - - // if one peer belongs to a higher priority torrent than the other one - // that one should be unchoked. - boost::shared_ptr t1 = m_torrent.lock(); - TORRENT_ASSERT(t1); - boost::shared_ptr t2 = rhs.associated_torrent().lock(); - TORRENT_ASSERT(t2); - - int prio1 = get_priority(upload_channel); - int prio2 = rhs.get_priority(upload_channel); - - if (prio1 != prio2) - return prio1 > prio2; - - // compare how many bytes they've sent us - size_type c1; - size_type c2; - c1 = downloaded_in_last_round(); - c2 = rhs.downloaded_in_last_round(); - - if (c1 != c2) return c1 > c2; - - if (m_settings.get_int(settings_pack::seed_choking_algorithm) - == settings_pack::round_robin) - { - // the amount uploaded since unchoked (not just in the last round) - c1 = uploaded_since_unchoked(); - c2 = rhs.uploaded_since_unchoked(); - - // the way the round-robin unchoker works is that it, - // by default, prioritizes any peer that is already unchoked. - // this maintain the status quo across unchoke rounds. However, - // peers that are unchoked, but have sent more than one quota - // since they were unchoked, they get de-prioritized. - - int pieces = m_settings.get_int(settings_pack::seeding_piece_quota); - // if a peer is already unchoked, and the number of bytes sent since it was unchoked - // is greater than the send quanta, then it's done with it' upload slot, and we - // can de-prioritize it - bool c1_quota_complete = !is_choked() && c1 > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); - bool c2_quota_complete = !rhs.is_choked() && c2 > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); - - // if c2 has completed a quanta, it shuold be de-prioritized - // and vice versa - if (c1_quota_complete < c2_quota_complete) return true; - if (c1_quota_complete > c2_quota_complete) return false; - - // if both peers have either completed a quanta, or not. - // keep unchoked peers prioritized over choked ones, to let - // peers keep working on uploading a full quanta - if (is_choked() < rhs.is_choked()) return true; - if (is_choked() > rhs.is_choked()) return false; - - // if the peers are still identical (say, they're both waiting to be unchoked) - // fall through and rely on the logic to prioritize peers who have waited - // the longest to be unchoked - } - else if (m_settings.get_int(settings_pack::seed_choking_algorithm) - == settings_pack::fastest_upload) - { - c1 = uploaded_in_last_round(); - c2 = rhs.uploaded_in_last_round(); - - // take torrent priority into account - c1 *= prio1; - c2 *= prio2; - - if (c1 > c2) return true; - if (c2 > c1) return false; - } - else if (m_settings.get_int(settings_pack::seed_choking_algorithm) - == settings_pack::anti_leech) - { - // the anti-leech seeding algorithm is based on the paper "Improving - // BitTorrent: A Simple Approach" from Chow et. al. and ranks peers based - // on how many pieces they have, prefering to unchoke peers that just - // started and peers that are close to completing. Like this: - // ^ - // | \ / | - // | \ / | - // | \ / | - // s | \ / | - // c | \ / | - // o | \ / | - // r | \ / | - // e | \ / | - // | \ / | - // | \ / | - // | \ / | - // | \ / | - // | V | - // +---------------------------+ - // 0% num have pieces 100% - int t1_total = t1->torrent_file().num_pieces(); - int t2_total = t2->torrent_file().num_pieces(); - int score1 = (num_have_pieces() < t1_total / 2 - ? t1_total - num_have_pieces() : num_have_pieces()) * 1000 / t1_total; - int score2 = (rhs.num_have_pieces() < t2_total / 2 - ? t2_total - rhs.num_have_pieces() : rhs.num_have_pieces()) * 1000 / t2_total; - if (score1 > score2) return true; - if (score2 > score1) return false; - } - - // prioritize the one that has waited the longest to be unchoked - // the round-robin unchoker relies on this logic. Don't change it - // without moving this into that unchoker logic - return m_last_unchoke < rhs.m_last_unchoke; - } - int peer_connection::get_priority(int channel) const { TORRENT_ASSERT(is_single_thread()); @@ -463,22 +318,6 @@ namespace libtorrent return prio; } - bool peer_connection::upload_rate_compare(peer_connection const* p) const - { - TORRENT_ASSERT(is_single_thread()); - size_type c1; - size_type c2; - - c1 = uploaded_in_last_round(); - c2 = p->uploaded_in_last_round(); - - // take torrent priority into account - c1 *= get_priority(upload_channel); - c2 *= p->get_priority(upload_channel); - - return c1 > c2; - } - void peer_connection::reset_choke_counters() { TORRENT_ASSERT(is_single_thread()); @@ -3909,8 +3748,6 @@ namespace libtorrent bool empty_download_queue = m_download_queue.empty(); - ptime now = time_now_hires(); - while (!m_request_queue.empty() && ((int)m_download_queue.size() < m_desired_queue_size || m_queued_time_critical > 0))