Commit 852b0b4b authored by Matt Whitlock's avatar Matt Whitlock
Browse files

respect file priority when selecting preview chunks

Previously, ChunkSelector would select preview chunks ahead of all
other chunks, regardless of file priority. This meant that files
prioritized as "Last" would nevertheless have all of their preview
chunks downloaded before all files prioritized as "First" or "Normal"
had finished downloading. This contradicts user expectation, which is
that scarce download bandwidth should not be devoted to files marked
"Last" until all files prioritized as "First" and "Normal" have been
completely downloaded.

This commit splits the existing PREVIEW_PRIORITY level into three
priority levels: FIRST_PREVIEW_PRIORITY, NORMAL_PREVIEW_PRIORITY, and
LAST_PREVIEW_PRIORITY. Chunks that ChunkManager determines to be
preview chunks have their priority levels boosted to the preview
priority level corresponding to their respective base priority level.

ChunkSelector's algorithm receives an overhaul that eliminates the
temporary std::list objects in favor of remembering the best (highest
priority, least number of downloaders) chunk during the iteration and
selecting that chunk if no chunk at the same priority level but with
no downloaders is found. The logic is ultimately equivalent to that
implemented in leastPeers(…) but will run faster since it requires no
heap allocations. Also, the new algorithm will not fail to select a
chunk that already has downloaders if there are no chunks that have no
downloaders. This should keep PieceDownloaders busier.
parent 36beec0c
......@@ -117,6 +117,14 @@ public:
priority = newpriority;
}
/// Is chunk part of a multimedia preview
bool isPreview() const
{
return priority == FIRST_PREVIEW_PRIORITY ||
priority == NORMAL_PREVIEW_PRIORITY ||
priority == LAST_PREVIEW_PRIORITY;
}
/// Is chunk excluded
bool isExcluded() const
{
......
......@@ -346,6 +346,34 @@ void ChunkManager::prioritise(Uint32 from, Uint32 to, Priority priority)
updateStats();
}
void ChunkManager::prioritisePreview(Uint32 from, Uint32 to)
{
if (from > to)
std::swap(from, to);
for (Uint32 i = from; i <= to && i < (Uint32)d->chunks.size(); ++i) {
Chunk *c = d->chunks[i];
Priority priority = c->getPriority();
switch (priority) {
case FIRST_PRIORITY:
priority = FIRST_PREVIEW_PRIORITY;
break;
case NORMAL_PRIORITY:
priority = NORMAL_PREVIEW_PRIORITY;
break;
case LAST_PRIORITY:
priority = LAST_PREVIEW_PRIORITY;
break;
default:
continue;
}
c->setPriority(priority);
}
updateStats();
}
void ChunkManager::exclude(Uint32 from, Uint32 to)
{
if (from > to)
......@@ -816,9 +844,9 @@ void ChunkManager::Private::setupPriorities()
} else if (tor.isMultimedia()) {
Uint32 nchunks = p->previewChunkRangeSize();
p->prioritise(0, nchunks, PREVIEW_PRIORITY);
p->prioritisePreview(0, nchunks);
if (tor.getNumChunks() > nchunks) {
p->prioritise(tor.getNumChunks() - nchunks, tor.getNumChunks() - 1, PREVIEW_PRIORITY);
p->prioritisePreview(tor.getNumChunks() - nchunks, tor.getNumChunks() - 1);
}
}
}
......@@ -958,7 +986,7 @@ void ChunkManager::Private::doPreviewPriority(TorrentFile &file)
if (file.getFirstChunk() == file.getLastChunk()) {
// prioritise whole file
p->prioritise(file.getFirstChunk(), file.getLastChunk(), PREVIEW_PRIORITY);
p->prioritisePreview(file.getFirstChunk(), file.getLastChunk());
return;
}
......@@ -966,9 +994,9 @@ void ChunkManager::Private::doPreviewPriority(TorrentFile &file)
if (!nchunks)
return;
p->prioritise(file.getFirstChunk(), file.getFirstChunk() + nchunks, PREVIEW_PRIORITY);
p->prioritisePreview(file.getFirstChunk(), file.getFirstChunk() + nchunks);
if (file.getLastChunk() - file.getFirstChunk() > nchunks) {
p->prioritise(file.getLastChunk() - nchunks, file.getLastChunk(), PREVIEW_PRIORITY);
p->prioritisePreview(file.getLastChunk() - nchunks, file.getLastChunk());
}
}
......@@ -993,9 +1021,21 @@ void ChunkManager::Private::dumpPriority(TorrentFile *tf)
for (Uint32 i = first; i <= last; i++) {
QString prio;
switch (chunks[i]->getPriority()) {
case FIRST_PREVIEW_PRIORITY:
prio = "First (Preview)";
break;
case FIRST_PRIORITY:
prio = "First";
break;
case NORMAL_PREVIEW_PRIORITY:
prio = "Normal (Preview)";
break;
case NORMAL_PRIORITY:
prio = "Normal";
break;
case LAST_PREVIEW_PRIORITY:
prio = "Last (Preview)";
break;
case LAST_PRIORITY:
prio = "Last";
break;
......@@ -1005,11 +1045,8 @@ void ChunkManager::Private::dumpPriority(TorrentFile *tf)
case EXCLUDED:
prio = "Excluded";
break;
case PREVIEW_PRIORITY:
prio = "Preview";
break;
default:
prio = "Normal";
prio = "(invalid)";
break;
}
Out(SYS_DIO | LOG_DEBUG) << i << " prio " << prio << endl;
......
......@@ -253,6 +253,13 @@ public:
*/
void prioritise(Uint32 from, Uint32 to, Priority priority);
/**
* Increases the priority of a range to preview priority.
* @param from First chunk in range
* @param to Last chunk in range
*/
void prioritisePreview(Uint32 from, Uint32 to);
/**
* Make sure that a range will not be downloaded.
* @param from First chunk in range
......
......@@ -133,10 +133,9 @@ bool ChunkSelector::select(PieceDownloader *pd, Uint32 &chunk)
{
const BitSet &bs = cman->getBitSet();
std::list<Uint32> preview;
std::list<Uint32> normal;
std::list<Uint32> first;
Uint32 sel = cman->getNumChunks() + 1;
Uint32 sel = ~Uint32();
Uint32 sel_dl = ~Uint32();
Priority sel_prio = EXCLUDED;
// sort the chunks every 2 seconds
if (sort_timer.getElapsedSinceUpdate() > 2000) {
......@@ -155,28 +154,33 @@ bool ChunkSelector::select(PieceDownloader *pd, Uint32 &chunk)
std::list<Uint32>::iterator tmp = itr;
++itr;
chunks.erase(tmp);
} else if (c->getPriority() < sel_prio) {
// we've already found a suitable chunk at a higher priority, so select that one
break;
} else if (pd->hasChunk(i)) {
// pd has to have the selected chunk and it needs to be not excluded
if (!downer->downloading(i)) {
// we have a chunk
Uint32 dl = downer->numDownloadersForChunk(i);
if (dl == 0) {
// we found a chunk that has no downloaders, so select it
sel = i;
break;
}
switch (cman->getChunk(i)->getPriority()) {
case PREVIEW_PRIORITY:
preview.push_back(i);
break;
case FIRST_PRIORITY:
first.push_back(i);
break;
case NORMAL_PRIORITY:
normal.push_back(i);
break;
default:
break;
// we found a chunk that has downloaders; remember it if it has fewer
// downloaders than the chunk we're already remembering (if any) and:
// - it has fewer than max_peers_per_chunk downloaders, or
// - we're in endgame mode, or
// - it is downloading very slowly
if (dl < sel_dl) {
const Uint32 max_peers_per_chunk = c->isPreview() ? 3 : 2;
ChunkDownload *cd;
if (dl < max_peers_per_chunk || downer->endgameMode() ||
((cd = downer->download(i)) && cd->getDownloadSpeed() < 100))
{
sel = i;
sel_dl = dl;
sel_prio = c->getPriority();
}
}
++itr;
} else
++itr;
......@@ -185,53 +189,8 @@ bool ChunkSelector::select(PieceDownloader *pd, Uint32 &chunk)
if (sel >= cman->getNumChunks())
return false;
// we have found one, now try to see if we cannot assign this PieceDownloader to a higher priority chunk
switch (cman->getChunk(sel)->getPriority()) {
case PREVIEW_PRIORITY:
chunk = sel;
return true;
case FIRST_PRIORITY:
if (preview.size() > 0) {
chunk = leastPeers(preview, sel, 3);
return true;
} else {
chunk = sel;
return true;
}
break;
case NORMAL_PRIORITY:
if (preview.size() > 0) {
chunk = leastPeers(preview, sel, 3);
return true;
} else if (first.size() > 0) {
chunk = leastPeers(first, sel, 2);
return true;
} else {
chunk = sel;
return true;
}
break;
case LAST_PRIORITY:
if (preview.size() > 0) {
chunk = leastPeers(preview, sel, 3);
return true;
} else if (first.size() > 0) {
chunk = leastPeers(first, sel, 2);
return true;
} else if (normal.size() > 0) {
chunk = leastPeers(normal, sel, 2);
return true;
} else {
chunk = sel;
return true;
}
break;
default:
chunk = sel;
return true;
}
return false;
chunk = sel;
return true;
}
void ChunkSelector::dataChecked(const BitSet &ok_chunks, Uint32 from, Uint32 to)
......
......@@ -50,7 +50,7 @@ void StreamingChunkSelector::init(ChunkManager *cman, Downloader *downer, PeerMa
preview_chunks.clear();
for (Uint32 i = 0; i <= range_end; i++)
if (cman->getChunk(i)->getPriority() == bt::PREVIEW_PRIORITY)
if (cman->getChunk(i)->isPreview())
preview_chunks.insert(i);
}
......@@ -191,13 +191,13 @@ void StreamingChunkSelector::reincluded(bt::Uint32 from, bt::Uint32 to)
initRange();
for (Uint32 chunk = from; chunk <= to; chunk++)
if (cman->getChunk(chunk)->getPriority() == bt::PREVIEW_PRIORITY)
if (cman->getChunk(chunk)->isPreview())
preview_chunks.insert(chunk);
}
void StreamingChunkSelector::reinsert(bt::Uint32 chunk)
{
if (cman->getChunk(chunk)->getPriority() == bt::PREVIEW_PRIORITY)
if (cman->getChunk(chunk)->isPreview())
preview_chunks.insert(chunk);
bt::ChunkSelector::reinsert(chunk);
......
......@@ -38,9 +38,11 @@ typedef Uint64 TimeStamp;
typedef enum {
// also leave some room if we want to add new priorities in the future
PREVIEW_PRIORITY = 60,
FIRST_PREVIEW_PRIORITY = 55,
FIRST_PRIORITY = 50,
NORMAL_PREVIEW_PRIORITY = 45,
NORMAL_PRIORITY = 40,
LAST_PREVIEW_PRIORITY = 35,
LAST_PRIORITY = 30,
ONLY_SEED_PRIORITY = 20,
EXCLUDED = 10,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment