add feature to create an affinity to pick adjecent pieces aligned to 4MiB extents. It's an attempt to improve disk I/O, by writing larger contiguous ranges of bytes. It's off by default.
This commit is contained in:
parent
ffd4b39b09
commit
07ab3b9739
|
@ -2702,6 +2702,7 @@ TORRENT_VERSION_NAMESPACE_2
|
||||||
static constexpr picker_flags_t backup1 = 13_bit;
|
static constexpr picker_flags_t backup1 = 13_bit;
|
||||||
static constexpr picker_flags_t backup2 = 14_bit;
|
static constexpr picker_flags_t backup2 = 14_bit;
|
||||||
static constexpr picker_flags_t end_game = 15_bit;
|
static constexpr picker_flags_t end_game = 15_bit;
|
||||||
|
static constexpr picker_flags_t extent_affinity = 16_bit;
|
||||||
|
|
||||||
// this is a bitmask of which features were enabled for this particular
|
// this is a bitmask of which features were enabled for this particular
|
||||||
// pick. The bits are defined in the picker_flags_t enum.
|
// pick. The bits are defined in the picker_flags_t enum.
|
||||||
|
|
|
@ -69,6 +69,7 @@ namespace libtorrent {
|
||||||
using prio_index_t = aux::strong_typedef<int, struct prio_index_tag_t>;
|
using prio_index_t = aux::strong_typedef<int, struct prio_index_tag_t>;
|
||||||
using picker_options_t = flags::bitfield_flag<std::uint16_t, struct picker_options_tag>;
|
using picker_options_t = flags::bitfield_flag<std::uint16_t, struct picker_options_tag>;
|
||||||
using download_queue_t = aux::strong_typedef<std::uint8_t, struct dl_queue_tag>;
|
using download_queue_t = aux::strong_typedef<std::uint8_t, struct dl_queue_tag>;
|
||||||
|
using piece_extent_t = aux::strong_typedef<int, struct piece_extent_tag>;
|
||||||
|
|
||||||
struct piece_count
|
struct piece_count
|
||||||
{
|
{
|
||||||
|
@ -141,6 +142,11 @@ namespace libtorrent {
|
||||||
// range of pieces.
|
// range of pieces.
|
||||||
static constexpr picker_options_t align_expanded_pieces = 6_bit;
|
static constexpr picker_options_t align_expanded_pieces = 6_bit;
|
||||||
|
|
||||||
|
// this will create an affinity to pick pieces in extents of 4 MiB, in an
|
||||||
|
// attempt to improve disk I/O by picking ranges of pieces (if pieces are
|
||||||
|
// small)
|
||||||
|
static constexpr picker_options_t piece_extent_affinity = 7_bit;
|
||||||
|
|
||||||
struct downloading_piece
|
struct downloading_piece
|
||||||
{
|
{
|
||||||
downloading_piece()
|
downloading_piece()
|
||||||
|
@ -469,6 +475,11 @@ namespace libtorrent {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
piece_extent_t extent_for(piece_index_t) const;
|
||||||
|
index_range<piece_index_t> extent_for(piece_extent_t) const;
|
||||||
|
|
||||||
|
void record_downloading_piece(piece_index_t const p);
|
||||||
|
|
||||||
int num_pad_blocks() const { return m_num_pad_blocks; }
|
int num_pad_blocks() const { return m_num_pad_blocks; }
|
||||||
|
|
||||||
span<block_info> mutable_blocks_for_piece(downloading_piece const& dp);
|
span<block_info> mutable_blocks_for_piece(downloading_piece const& dp);
|
||||||
|
@ -741,6 +752,13 @@ namespace libtorrent {
|
||||||
// tracks the number of blocks in a specific piece that are pad blocks
|
// tracks the number of blocks in a specific piece that are pad blocks
|
||||||
std::unordered_map<piece_index_t, int> m_pads_in_piece;
|
std::unordered_map<piece_index_t, int> m_pads_in_piece;
|
||||||
|
|
||||||
|
// when the adjecent_piece affinity is enabled, this contains the most
|
||||||
|
// recent "extents" of adjecent pieces that have been requested from
|
||||||
|
// this is mutable because it's updated by functions to pick pieces, which
|
||||||
|
// are const. That's an efficient place to update it, since it's being
|
||||||
|
// traversed already.
|
||||||
|
mutable std::vector<piece_extent_t> m_recent_extents;
|
||||||
|
|
||||||
// the number of bits set in the m_pad_blocks bitfield, i.e.
|
// the number of bits set in the m_pad_blocks bitfield, i.e.
|
||||||
// the number of blocks marked as pads
|
// the number of blocks marked as pads
|
||||||
int m_num_pad_blocks = 0;
|
int m_num_pad_blocks = 0;
|
||||||
|
|
|
@ -734,6 +734,12 @@ namespace libtorrent {
|
||||||
// preferred in the routing table.
|
// preferred in the routing table.
|
||||||
dht_prefer_verified_node_ids,
|
dht_prefer_verified_node_ids,
|
||||||
|
|
||||||
|
// when this is true, create an affinity for downloading 4 MiB extents
|
||||||
|
// of adjecent pieces. This is an attempt to achieve better disk I/O
|
||||||
|
// throughput by downloading larger extents of bytes, for torrents with
|
||||||
|
// small piece sizes
|
||||||
|
piece_extent_affinity,
|
||||||
|
|
||||||
max_bool_setting_internal
|
max_bool_setting_internal
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -370,3 +370,21 @@ TORRENT_TEST(disable_disk_cache)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity)
|
||||||
|
{
|
||||||
|
using namespace lt;
|
||||||
|
run_test(
|
||||||
|
[](lt::session& ses0, lt::session& ses1)
|
||||||
|
{
|
||||||
|
settings_pack p;
|
||||||
|
p.set_bool(settings_pack::piece_extent_affinity, true);
|
||||||
|
ses0.apply_settings(p);
|
||||||
|
ses1.apply_settings(p);
|
||||||
|
},
|
||||||
|
[](lt::session&, lt::alert const*) {},
|
||||||
|
[](std::shared_ptr<lt::session> ses[2]) {
|
||||||
|
TEST_EQUAL(is_seed(*ses[0]), true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -2258,6 +2258,7 @@ namespace {
|
||||||
constexpr picker_flags_t picker_log_alert::backup1;
|
constexpr picker_flags_t picker_log_alert::backup1;
|
||||||
constexpr picker_flags_t picker_log_alert::backup2;
|
constexpr picker_flags_t picker_log_alert::backup2;
|
||||||
constexpr picker_flags_t picker_log_alert::end_game;
|
constexpr picker_flags_t picker_log_alert::end_game;
|
||||||
|
constexpr picker_flags_t picker_log_alert::extent_affinity;
|
||||||
|
|
||||||
std::string picker_log_alert::message() const
|
std::string picker_log_alert::message() const
|
||||||
{
|
{
|
||||||
|
@ -2279,6 +2280,7 @@ namespace {
|
||||||
"backup1 ",
|
"backup1 ",
|
||||||
"backup2 ",
|
"backup2 ",
|
||||||
"end_game "
|
"end_game "
|
||||||
|
"extent_affinity "
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string ret = peer_alert::message();
|
std::string ret = peer_alert::message();
|
||||||
|
|
|
@ -928,7 +928,12 @@ namespace libtorrent {
|
||||||
// request blocks from the same piece
|
// request blocks from the same piece
|
||||||
ret |= piece_picker::reverse;
|
ret |= piece_picker::reverse;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_settings.get_bool(settings_pack::piece_extent_affinity)
|
||||||
|
&& t->num_time_critical_pieces() == 0)
|
||||||
|
ret |= piece_picker::piece_extent_affinity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_settings.get_bool(settings_pack::prioritize_partial_pieces))
|
if (m_settings.get_bool(settings_pack::prioritize_partial_pieces))
|
||||||
|
|
|
@ -46,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "libtorrent/performance_counters.hpp" // for counters
|
#include "libtorrent/performance_counters.hpp" // for counters
|
||||||
#include "libtorrent/alert_types.hpp" // for picker_log_alert
|
#include "libtorrent/alert_types.hpp" // for picker_log_alert
|
||||||
#include "libtorrent/download_priority.hpp"
|
#include "libtorrent/download_priority.hpp"
|
||||||
|
#include "libtorrent/disk_interface.hpp" // for default_block_size
|
||||||
|
|
||||||
#if TORRENT_USE_ASSERTS
|
#if TORRENT_USE_ASSERTS
|
||||||
#include "libtorrent/peer_connection.hpp"
|
#include "libtorrent/peer_connection.hpp"
|
||||||
|
@ -117,6 +118,7 @@ namespace libtorrent {
|
||||||
constexpr picker_options_t piece_picker::sequential;
|
constexpr picker_options_t piece_picker::sequential;
|
||||||
constexpr picker_options_t piece_picker::time_critical_mode;
|
constexpr picker_options_t piece_picker::time_critical_mode;
|
||||||
constexpr picker_options_t piece_picker::align_expanded_pieces;
|
constexpr picker_options_t piece_picker::align_expanded_pieces;
|
||||||
|
constexpr picker_options_t piece_picker::piece_extent_affinity;
|
||||||
|
|
||||||
constexpr download_queue_t piece_picker::piece_pos::piece_downloading;
|
constexpr download_queue_t piece_picker::piece_pos::piece_downloading;
|
||||||
constexpr download_queue_t piece_picker::piece_pos::piece_full;
|
constexpr download_queue_t piece_picker::piece_pos::piece_full;
|
||||||
|
@ -127,6 +129,9 @@ namespace libtorrent {
|
||||||
constexpr download_queue_t piece_picker::piece_pos::piece_downloading_reverse;
|
constexpr download_queue_t piece_picker::piece_pos::piece_downloading_reverse;
|
||||||
constexpr download_queue_t piece_picker::piece_pos::piece_full_reverse;
|
constexpr download_queue_t piece_picker::piece_pos::piece_full_reverse;
|
||||||
|
|
||||||
|
// the max number of blocks to create an affinity for
|
||||||
|
constexpr int max_piece_affinity_extent = 4 * 1024 * 1024 / default_block_size;
|
||||||
|
|
||||||
piece_picker::piece_picker(int const blocks_per_piece
|
piece_picker::piece_picker(int const blocks_per_piece
|
||||||
, int const blocks_in_last_piece, int const total_num_pieces)
|
, int const blocks_in_last_piece, int const total_num_pieces)
|
||||||
: m_priority_boundaries(1, m_pieces.end_index())
|
: m_priority_boundaries(1, m_pieces.end_index())
|
||||||
|
@ -2129,6 +2134,41 @@ namespace {
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO: Is it a good idea that this affinity takes precedence over
|
||||||
|
// piece priority?
|
||||||
|
if (options & piece_extent_affinity)
|
||||||
|
{
|
||||||
|
int to_erase = -1;
|
||||||
|
int idx = -1;
|
||||||
|
for (piece_extent_t const e : m_recent_extents)
|
||||||
|
{
|
||||||
|
++idx;
|
||||||
|
bool have_all = true;
|
||||||
|
for (piece_index_t const p : extent_for(e))
|
||||||
|
{
|
||||||
|
if (!m_piece_map[p].have()) have_all = false;
|
||||||
|
if (!is_piece_free(p, pieces)) continue;
|
||||||
|
|
||||||
|
ret |= picker_log_alert::extent_affinity;
|
||||||
|
|
||||||
|
num_blocks = add_blocks(p, pieces
|
||||||
|
, interesting_blocks, backup_blocks
|
||||||
|
, backup_blocks2, num_blocks
|
||||||
|
, prefer_contiguous_blocks, peer, suggested_pieces
|
||||||
|
, options);
|
||||||
|
if (num_blocks <= 0)
|
||||||
|
{
|
||||||
|
// if we have all pieces belonging to this extent, remove it
|
||||||
|
if (to_erase != -1) m_recent_extents.erase(m_recent_extents.begin() + to_erase);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we have all pieces belonging to this extent, remove it
|
||||||
|
if (have_all) to_erase = idx;
|
||||||
|
}
|
||||||
|
if (to_erase != -1) m_recent_extents.erase(m_recent_extents.begin() + to_erase);
|
||||||
|
}
|
||||||
|
|
||||||
for (piece_index_t i : m_pieces)
|
for (piece_index_t i : m_pieces)
|
||||||
{
|
{
|
||||||
pc.inc_stats_counter(counters::piece_picker_rare_loops);
|
pc.inc_stats_counter(counters::piece_picker_rare_loops);
|
||||||
|
@ -2987,6 +3027,68 @@ get_out:
|
||||||
return info[block.block_index].state == block_info::state_finished;
|
return info[block.block_index].state == block_info::state_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
piece_extent_t piece_picker::extent_for(piece_index_t const p) const
|
||||||
|
{
|
||||||
|
int const extent_size = max_piece_affinity_extent / m_blocks_per_piece;
|
||||||
|
return piece_extent_t{static_cast<int>(p) / extent_size};
|
||||||
|
}
|
||||||
|
|
||||||
|
index_range<piece_index_t> piece_picker::extent_for(piece_extent_t const e) const
|
||||||
|
{
|
||||||
|
int const extent_size = max_piece_affinity_extent / m_blocks_per_piece;
|
||||||
|
int const begin = static_cast<int>(e) * extent_size;
|
||||||
|
int const end = std::min(begin + extent_size, num_pieces());
|
||||||
|
return { piece_index_t{begin}, piece_index_t{end}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void piece_picker::record_downloading_piece(piece_index_t const p)
|
||||||
|
{
|
||||||
|
// if a single piece is large enough, don't bother with the affinity of
|
||||||
|
// adjecent pieces.
|
||||||
|
if (m_blocks_per_piece >= max_piece_affinity_extent) return;
|
||||||
|
|
||||||
|
piece_extent_t const this_extent = extent_for(p);
|
||||||
|
|
||||||
|
// if the extent is already in the list, nothing to do
|
||||||
|
if (std::find(m_recent_extents.begin()
|
||||||
|
, m_recent_extents.end(), this_extent) != m_recent_extents.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
download_priority_t const this_prio = piece_priority(p);
|
||||||
|
|
||||||
|
// figure out if it's worth recording this downloading piece
|
||||||
|
// if we already have all blocks in this extent, there's no point in
|
||||||
|
// adding it
|
||||||
|
bool have_all = true;
|
||||||
|
|
||||||
|
for (auto const piece : extent_for(this_extent))
|
||||||
|
{
|
||||||
|
if (piece == p) continue;
|
||||||
|
|
||||||
|
if (!m_piece_map[piece].have()) have_all = false;
|
||||||
|
|
||||||
|
// if at least one piece in this extent has a different priority than
|
||||||
|
// the one we just started downloading, don't create an affinity for
|
||||||
|
// adjecent pieces. This probably means the pieces belong to different
|
||||||
|
// files, or that some other mechanism determining the priority should
|
||||||
|
// take precedence.
|
||||||
|
if (piece_priority(piece) != this_prio) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we already have all the *other* pieces in this extent, there's no
|
||||||
|
// need to inflate their priorities
|
||||||
|
if (have_all) return;
|
||||||
|
|
||||||
|
// TODO: should 5 be configurable?
|
||||||
|
if (m_recent_extents.size() < 5)
|
||||||
|
m_recent_extents.push_back(this_extent);
|
||||||
|
|
||||||
|
// limit the number of extent affinities active at any given time to limit
|
||||||
|
// the cost of checking them. Also, don't replace them, commit to
|
||||||
|
// finishing them before starting another extent. This is analoguous to
|
||||||
|
// limiting the number of partial pieces.
|
||||||
|
}
|
||||||
|
|
||||||
// options may be 0 or piece_picker::reverse
|
// options may be 0 or piece_picker::reverse
|
||||||
// returns false if the block could not be marked as downloading
|
// returns false if the block could not be marked as downloading
|
||||||
bool piece_picker::mark_as_downloading(piece_block const block
|
bool piece_picker::mark_as_downloading(piece_block const block
|
||||||
|
@ -3020,6 +3122,17 @@ get_out:
|
||||||
|
|
||||||
if (prio >= 0 && !m_dirty) update(prio, p.index);
|
if (prio >= 0 && !m_dirty) update(prio, p.index);
|
||||||
|
|
||||||
|
// if the piece extent affinity is enabled, (maybe) record downloading a
|
||||||
|
// block from this piece to make other peers prefer adjecent pieces
|
||||||
|
// if reverse is set, don't encourage other peers to pick nearby
|
||||||
|
// pieces, as that's assumed to be low priority.
|
||||||
|
// if time critical mode is enabled, we're likely to either download
|
||||||
|
// adjacent pieces anyway, but more importantly, we don't want to
|
||||||
|
// create artificially higher priority for adjecent pieces if they
|
||||||
|
// aren't important or urgent
|
||||||
|
if (options & piece_extent_affinity)
|
||||||
|
record_downloading_piece(block.piece_index);
|
||||||
|
|
||||||
auto const dp = add_download_piece(block.piece_index);
|
auto const dp = add_download_piece(block.piece_index);
|
||||||
auto const binfo = mutable_blocks_for_piece(*dp);
|
auto const binfo = mutable_blocks_for_piece(*dp);
|
||||||
block_info& info = binfo[block.block_index];
|
block_info& info = binfo[block.block_index];
|
||||||
|
|
|
@ -208,6 +208,7 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
|
||||||
SET(proxy_tracker_connections, true, nullptr),
|
SET(proxy_tracker_connections, true, nullptr),
|
||||||
SET(enable_ip_notifier, true, &session_impl::update_ip_notifier),
|
SET(enable_ip_notifier, true, &session_impl::update_ip_notifier),
|
||||||
SET(dht_prefer_verified_node_ids, true, &session_impl::update_dht_settings),
|
SET(dht_prefer_verified_node_ids, true, &session_impl::update_dht_settings),
|
||||||
|
SET(piece_extent_affinity, false, nullptr),
|
||||||
}});
|
}});
|
||||||
|
|
||||||
aux::array<int_setting_entry_t, settings_pack::num_int_settings> const int_settings
|
aux::array<int_setting_entry_t, settings_pack::num_int_settings> const int_settings
|
||||||
|
|
|
@ -112,12 +112,13 @@ std::shared_ptr<piece_picker> setup_picker(
|
||||||
char const* availability
|
char const* availability
|
||||||
, char const* have_str
|
, char const* have_str
|
||||||
, char const* priority
|
, char const* priority
|
||||||
, char const* partial)
|
, char const* partial
|
||||||
|
, int num_blocks_per_piece = blocks_per_piece)
|
||||||
{
|
{
|
||||||
const int num_pieces = int(strlen(availability));
|
const int num_pieces = int(strlen(availability));
|
||||||
TORRENT_ASSERT(int(strlen(have_str)) == num_pieces);
|
TORRENT_ASSERT(int(strlen(have_str)) == num_pieces);
|
||||||
|
|
||||||
std::shared_ptr<piece_picker> p = std::make_shared<piece_picker>(blocks_per_piece, blocks_per_piece, num_pieces);
|
std::shared_ptr<piece_picker> p = std::make_shared<piece_picker>(num_blocks_per_piece, num_blocks_per_piece, num_pieces);
|
||||||
|
|
||||||
for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i)
|
for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i)
|
||||||
{
|
{
|
||||||
|
@ -193,7 +194,7 @@ std::shared_ptr<piece_picker> setup_picker(
|
||||||
{
|
{
|
||||||
if (!have[i]) continue;
|
if (!have[i]) continue;
|
||||||
p->we_have(i);
|
p->we_have(i);
|
||||||
for (int j = 0; j < blocks_per_piece; ++j)
|
for (int j = 0; j < num_blocks_per_piece; ++j)
|
||||||
TEST_CHECK(p->is_finished(piece_block(i, j)));
|
TEST_CHECK(p->is_finished(piece_block(i, j)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2146,5 +2147,203 @@ TORRENT_TEST(pad_blocks_some_wanted)
|
||||||
TEST_EQUAL(p->want().pad_blocks, 2);
|
TEST_EQUAL(p->want().pad_blocks, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<piece_block> full_piece(int const p, int const blocks)
|
||||||
|
{
|
||||||
|
std::vector<piece_block> ret;
|
||||||
|
for (int i = 0;i < blocks; ++i)
|
||||||
|
ret.push_back(piece_block(piece_index_t{p}, i));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
void mark_downloading(std::shared_ptr<piece_picker> const& p, std::vector<piece_block> const blocks
|
||||||
|
, torrent_peer* const peer, picker_options_t const opts)
|
||||||
|
{
|
||||||
|
for (auto const& b : blocks)
|
||||||
|
p->mark_as_downloading(b, peer, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity)
|
||||||
|
{
|
||||||
|
int const blocks = 64;
|
||||||
|
// these are 2 extents. the first 4 pieces and the last 4 pieces
|
||||||
|
auto const have_none = " ";
|
||||||
|
auto const have_all = "********";
|
||||||
|
|
||||||
|
auto p = setup_picker("33133233", have_none, "", "", blocks);
|
||||||
|
|
||||||
|
std::vector<piece_block> picked = pick_pieces(p, have_all, blocks, 0, &tmp0
|
||||||
|
, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(2, blocks));
|
||||||
|
mark_downloading(p, full_piece(2, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// without the piece_extent_affinity, we would pick piece 5, because of
|
||||||
|
// availability
|
||||||
|
picked = pick_pieces(p, have_all, blocks, 0, &tmp1);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(5, blocks));
|
||||||
|
mark_downloading(p, full_piece(5, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// with piece_extent_affinity, we would pick piece 0, because it's the same
|
||||||
|
// extent as the piece we just picked
|
||||||
|
picked = pick_pieces(p, have_all, blocks, 0, &tmp2, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(0, blocks));
|
||||||
|
mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// then we should pick piece 1
|
||||||
|
picked = pick_pieces(p, have_all, blocks, 0, &tmp3, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(1, blocks));
|
||||||
|
mark_downloading(p, full_piece(1, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// then we should pick piece 3. The last piece of the extent
|
||||||
|
picked = pick_pieces(p, have_all, blocks, 0, &tmp4, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(3, blocks));
|
||||||
|
mark_downloading(p, full_piece(3, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity_priority)
|
||||||
|
{
|
||||||
|
int const blocks = 64;
|
||||||
|
auto const have_none = " ";
|
||||||
|
auto const have_all = "********";
|
||||||
|
|
||||||
|
auto p = setup_picker("33333233", have_none, "43444444", "", blocks);
|
||||||
|
// we pick piece 2. Since piece 1 has a different priority this should not
|
||||||
|
// create an affinity for the extent
|
||||||
|
mark_downloading(p, full_piece(2, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// so next piece to be picked will *not* be the extent, but piece 5, which
|
||||||
|
// has the lowest availability
|
||||||
|
|
||||||
|
std::vector<piece_block> picked = pick_pieces(p, have_all, blocks, 0, &tmp1
|
||||||
|
, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(5, blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity_large_pieces)
|
||||||
|
{
|
||||||
|
int const blocks = 256;
|
||||||
|
auto const have_none = " ";
|
||||||
|
auto const have_all = "********";
|
||||||
|
|
||||||
|
auto p = setup_picker("33333233", have_none, "", "", blocks);
|
||||||
|
// we pick piece 2. Since the pieces are so large (4 MiB), there is no
|
||||||
|
// affinity for piece extents.
|
||||||
|
mark_downloading(p, full_piece(2, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// so next piece to be picked will *not* be the extent, but piece 5, which
|
||||||
|
// has the next lowest availability
|
||||||
|
std::vector<piece_block> picked = pick_pieces(p, have_all, blocks, 0, &tmp1
|
||||||
|
, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(5, blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity_active_limit)
|
||||||
|
{
|
||||||
|
// an extent is two pieces wide, 6 extents total.
|
||||||
|
// make ure we limit the number of extents to 5
|
||||||
|
int const blocks = 128;
|
||||||
|
auto const have_none = " ";
|
||||||
|
|
||||||
|
auto p = setup_picker("333333333333", have_none, "444444444455", "", blocks);
|
||||||
|
// open up the first 5 extents
|
||||||
|
mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(2, blocks), &tmp1, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(4, blocks), &tmp2, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(6, blocks), &tmp3, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(8, blocks), &tmp4, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// this should not open up another extent. We should still have a bias
|
||||||
|
// towards pieces 1, 3, 5, 7 and 9.
|
||||||
|
mark_downloading(p, full_piece(10, blocks), &tmp5, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// a peer that only has piece 0, 1, 10, 11, will always pick 1, never 11,
|
||||||
|
// even though 10 and 11 have higher priority
|
||||||
|
|
||||||
|
std::vector<piece_block> picked = pick_pieces(p, "** **", blocks, 0, &tmp1
|
||||||
|
, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(1, blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity_clear_done)
|
||||||
|
{
|
||||||
|
// an extent is two pieces wide, 7 extents total.
|
||||||
|
// make sure we remove an active extent when we have all the pieces, and
|
||||||
|
// allow a new extent to be added
|
||||||
|
int const blocks = 128;
|
||||||
|
auto const have_none = " ";
|
||||||
|
|
||||||
|
auto p = setup_picker("33333333333333", have_none, "44444444444455", "", blocks);
|
||||||
|
// open up the first 5 extents
|
||||||
|
mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(2, blocks), &tmp1, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(4, blocks), &tmp2, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(6, blocks), &tmp3, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(8, blocks), &tmp4, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// now all 5 extents are in use, if we finish a whole extent, it should be
|
||||||
|
// removed from the list
|
||||||
|
p->we_have(piece_index_t{0});
|
||||||
|
p->we_have(piece_index_t{1});
|
||||||
|
|
||||||
|
// we need to invoke the piece picker once to detect and reap this full
|
||||||
|
// extent
|
||||||
|
pick_pieces(p, "**************", blocks, 0, &tmp1, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// this *should* open up another extent. We should still have a bias
|
||||||
|
// towards pieces 1, 3, 5, 7 and 9.
|
||||||
|
mark_downloading(p, full_piece(10, blocks), &tmp5, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// a peer that only has piece 10, 11, 12, 13 will always pick 11, since it's
|
||||||
|
// part of an extent that was just opened, never 12 or 13 even though they
|
||||||
|
// have higher priority
|
||||||
|
std::vector<piece_block> picked = pick_pieces(p, " ****", blocks, 0, &tmp1
|
||||||
|
, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(11, blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
TORRENT_TEST(piece_extent_affinity_no_duplicates)
|
||||||
|
{
|
||||||
|
// an extent is 8 pieces wide, 3 extents total.
|
||||||
|
// make sure that downloading pieces from the same extent don't create
|
||||||
|
// multiple entries in the recent-extent list, but they all use a single
|
||||||
|
// entry
|
||||||
|
int const blocks = 32;
|
||||||
|
auto const have_none = " ";
|
||||||
|
|
||||||
|
auto p = setup_picker("333333333333333333333333", have_none
|
||||||
|
, "444444444444444444444455", "", blocks);
|
||||||
|
// download 5 pieces from the first extent
|
||||||
|
mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(2, blocks), &tmp1, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(4, blocks), &tmp2, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(6, blocks), &tmp3, options | piece_picker::piece_extent_affinity);
|
||||||
|
mark_downloading(p, full_piece(1, blocks), &tmp4, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// since all these belong to the same extent (0), there should be a single
|
||||||
|
// entry in the recent extent list. Make sure that it's possible to open up a
|
||||||
|
// second extent, to show that all 5 entries weren't used up by 5 duplicates
|
||||||
|
// of 0.
|
||||||
|
// opens up extent 1
|
||||||
|
mark_downloading(p, full_piece(8, blocks), &tmp5, options | piece_picker::piece_extent_affinity);
|
||||||
|
|
||||||
|
// now, from a peer that doesn't have anything from the first extent, still
|
||||||
|
// pick from the second extent even though the last two pieces have higher
|
||||||
|
// priority.
|
||||||
|
std::vector<piece_block> picked = pick_pieces(p, " ****************", blocks, 0, &tmp1
|
||||||
|
, options | piece_picker::piece_extent_affinity);
|
||||||
|
TEST_CHECK(verify_pick(p, picked));
|
||||||
|
TEST_CHECK(picked == full_piece(9, blocks));
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: 2 test picking with partial pieces and other peers present so that both
|
//TODO: 2 test picking with partial pieces and other peers present so that both
|
||||||
// backup_pieces and backup_pieces2 are used
|
// backup_pieces and backup_pieces2 are used
|
||||||
|
|
Loading…
Reference in New Issue