[tahoe-dev] [tahoe-lafs] #1268: downloader/share: coalesce mutiple Share.loop() calls

tahoe-lafs trac at tahoe-lafs.org
Tue Nov 23 23:36:04 UTC 2010


#1268: downloader/share: coalesce mutiple Share.loop() calls
---------------------------+------------------------------------------------
 Reporter:  warner         |           Owner:           
     Type:  defect         |          Status:  new      
 Priority:  major          |       Milestone:  undecided
Component:  code-encoding  |         Version:  1.8.0    
 Keywords:                 |   Launchpad Bug:           
---------------------------+------------------------------------------------
 As alluded to in comment:92:ticket:1170 , the behavior of
 src/allmydata/immutable/downloader/share.py (in the {{{Share}}} class)
 should be improved. Each time a data-fetch response arrives from the
 server (either for a data block or for a couple of hash nodes), a call
 to {{{Share.loop}}} is queued. When run, {{{loop}}} recomputes the
 desire/satisfaction bitmap (a {{{Spans}}} instance), which takes a
 nontrivial amount of time. (although much less than it used to, before
 some of the issues in #1170 were fixed).

 The problem is that we're recomputing it far too many times, and most of
 them are redundant. For example, supposed we get responses to 4 requests
 in a big bunch (because they all arrived in the same network packet).
 Foolscap will queue an eventual-send for the response Deferreds all on
 the same reactor turn. When those fire, they'll update the
 what-data-we've-received {{{DataSpans}}} and then queue a call to
 loop(). All four messages will be processed before the first call to
 loop() occurs.

 The first loop() pass will process the {{{received}}} spans, deliver the
 completed block to the caller that asked for it, and then update the
 desire/satisfaction bitmaps in preparation for whatever the next block
 might be. The second loop() pass will recompute the bitmaps, even though
 no changes have occurred (no new blocks were requested, no new responses
 received). The third and fourth loop() passes will do the same.

 attachment:viz-3.png:ticket:1170 shows a bit of it: the pale yellow
 candlestick-like shapes (just below the "misc" label) are the calls to
 measure satisfaction and recompute the desire bitmap. The first call
 spends a lot of time in satisfy() (to deliver the completed block), and
 then a moderare amount of time in desire() (to figure out what we need
 next). The second and third calls both spend a tiny amount of time in
 satisfy() (since there are no completed blocks ready to be delivered),
 but then spend the same moderate amount of time in desire() recomputing
 the same bitmap as before.

 This gets worse when there are a lot of loop() calls queued, which means
 it gets worse when there are lots of (small) hash node requests coming
 back. As a result, the amount of wasted time is larger just after
 segments which trigger most hash-node requests. This distribution is an
 interesting function (probably one of those exciting sequences in the
 [http://oeis.org/ OEIS]), but basically it's zero for all odd-numbered
 segments, and gets larger as you switch over higher branches in a binary
 tree (so the max is for seg0, the next highest is for NUMSEGS/2, third
 highest is for NUMSEGS/4 and 3*NUMSEGS/4, etc).

 attachment:180c2-viz-delays.png:ticket:1170 shows this variation: the
 time between the end of the last pink block() request for any given
 segment, and the end of the segment() request, is mostly filled with
 this wasted recomputation. The time spent there is larger for some
 segments than others. In the example chart, the largest set of block
 requests (for the NUMSEGS/2 midpoint segment) has about 70ms of wasted
 time, whereas the very next segment (NUMSEGS/2+1, which needs no extra
 hash nodes) only spends about 10ms in that phase.

 The total amount of time wasted scales (linearly, I think) with NUMSEGS.
 It should also scale linearly with 'k', since there's a separate Share
 instance (and thus a whole set of these queued loop() calls) for each
 active share.

 This problem is hypothesized to be involved with #1264 (slow downloads
 for large k), although the rough measurements I did for #1170 suggest
 that the time spent is too small (8% for a local one-CPU download of a
 12MB file) to explain #1264.

 The fix will be to have {{{Share}}} refrain from queueing redundant
 calls to {{{loop()}}}:

 * factor out all the places that do {{{eventually(self.loop)}}} into a
   single {{{schedule_loop()}}} function
 * change {{{schedule_loop()}}} to check a flag:
  * if unset, set the flag and call {{{eventually(self.loop)}}}
  * if set, do nothing
 * clear the flag at the start of {{{loop()}}}

 Then we should re-run the #1264 tests to see if there's a significant
 change.

-- 
Ticket URL: <http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1268>
tahoe-lafs <http://tahoe-lafs.org>
secure decentralized storage


More information about the tahoe-dev mailing list