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

add feature to create an affinity to pick adjecent pieces #3913

Merged
merged 1 commit into from
Aug 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions include/libtorrent/alert_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2702,6 +2702,7 @@ TORRENT_VERSION_NAMESPACE_2
static constexpr picker_flags_t backup1 = 13_bit;
static constexpr picker_flags_t backup2 = 14_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
// pick. The bits are defined in the picker_flags_t enum.
Expand Down
18 changes: 18 additions & 0 deletions include/libtorrent/piece_picker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ namespace libtorrent {
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 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
{
Expand Down Expand Up @@ -141,6 +142,11 @@ namespace libtorrent {
// range of pieces.
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
{
downloading_piece()
Expand Down Expand Up @@ -469,6 +475,11 @@ namespace libtorrent {

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; }

span<block_info> mutable_blocks_for_piece(downloading_piece const& dp);
Expand Down Expand Up @@ -741,6 +752,13 @@ namespace libtorrent {
// tracks the number of blocks in a specific piece that are pad blocks
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 blocks marked as pads
int m_num_pad_blocks = 0;
Expand Down
6 changes: 6 additions & 0 deletions include/libtorrent/settings_pack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,12 @@ namespace libtorrent {
// preferred in the routing table.
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
};

Expand Down
18 changes: 18 additions & 0 deletions simulation/test_transfer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
);
}

2 changes: 2 additions & 0 deletions src/alert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2258,6 +2258,7 @@ namespace {
constexpr picker_flags_t picker_log_alert::backup1;
constexpr picker_flags_t picker_log_alert::backup2;
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
{
Expand All @@ -2279,6 +2280,7 @@ namespace {
"backup1 ",
"backup2 ",
"end_game "
"extent_affinity "
};

std::string ret = peer_alert::message();
Expand Down
7 changes: 6 additions & 1 deletion src/peer_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,12 @@ namespace libtorrent {
// request blocks from the same piece
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))
Expand Down
113 changes: 113 additions & 0 deletions src/piece_picker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/performance_counters.hpp" // for counters
#include "libtorrent/alert_types.hpp" // for picker_log_alert
#include "libtorrent/download_priority.hpp"
#include "libtorrent/disk_interface.hpp" // for default_block_size

#if TORRENT_USE_ASSERTS
#include "libtorrent/peer_connection.hpp"
Expand Down Expand Up @@ -117,6 +118,7 @@ namespace libtorrent {
constexpr picker_options_t piece_picker::sequential;
constexpr picker_options_t piece_picker::time_critical_mode;
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_full;
Expand All @@ -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_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
, int const blocks_in_last_piece, int const total_num_pieces)
: m_priority_boundaries(1, m_pieces.end_index())
Expand Down Expand Up @@ -2129,6 +2134,41 @@ namespace {
}
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)
{
pc.inc_stats_counter(counters::piece_picker_rare_loops);
Expand Down Expand Up @@ -2987,6 +3027,68 @@ namespace {
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
// returns false if the block could not be marked as downloading
bool piece_picker::mark_as_downloading(piece_block const block
Expand Down Expand Up @@ -3020,6 +3122,17 @@ namespace {

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 binfo = mutable_blocks_for_piece(*dp);
block_info& info = binfo[block.block_index];
Expand Down
1 change: 1 addition & 0 deletions src/settings_pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
SET(proxy_tracker_connections, true, nullptr),
SET(enable_ip_notifier, true, &session_impl::update_ip_notifier),
SET(dht_prefer_verified_node_ids, true, &session_impl::update_dht_settings),
SET(piece_extent_affinity, false, nullptr),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's off by default

}});

aux::array<int_setting_entry_t, settings_pack::num_int_settings> const int_settings
Expand Down