Skip to content

Commit

Permalink
add feature to create an affinity to pick adjecent pieces aligned to …
Browse files Browse the repository at this point in the history
…4MiB extents. It's an attempt to improve disk I/O, by writing larger contiguous ranges of bytes. It's off by default.
  • Loading branch information
arvidn committed Aug 17, 2019
1 parent ffd4b39 commit 07ab3b9
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 4 deletions.
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),
}});

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

0 comments on commit 07ab3b9

Please sign in to comment.