Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(629)

Side by Side Diff: net/disk_cache/v3/eviction_v3.cc

Issue 15203004: Disk cache: Reference CL for the implementation of file format version 3. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: IndexTable review Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « net/disk_cache/v3/eviction_v3.h ('k') | net/disk_cache/v3/index_table.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // The eviction policy is a very simple pure LRU, so the elements at the end of
6 // the list are evicted until kCleanUpMargin free space is available. There is
7 // only one list in use (Rankings::NO_USE), and elements are sent to the front
8 // of the list whenever they are accessed.
9
10 // The new (in-development) eviction policy adds re-use as a factor to evict
11 // an entry. The story so far:
12
13 // Entries are linked on separate lists depending on how often they are used.
14 // When we see an element for the first time, it goes to the NO_USE list; if
15 // the object is reused later on, we move it to the LOW_USE list, until it is
16 // used kHighUse times, at which point it is moved to the HIGH_USE list.
17 // Whenever an element is evicted, we move it to the DELETED list so that if the
18 // element is accessed again, we remember the fact that it was already stored
19 // and maybe in the future we don't evict that element.
20
21 // When we have to evict an element, first we try to use the last element from
22 // the NO_USE list, then we move to the LOW_USE and only then we evict an entry
23 // from the HIGH_USE. We attempt to keep entries on the cache for at least
24 // kTargetTime hours (with frequently accessed items stored for longer periods),
25 // but if we cannot do that, we fall-back to keep each list roughly the same
26 // size so that we have a chance to see an element again and move it to another
27 // list.
28
29 #include "net/disk_cache/v3/eviction_v3.h"
30
31 #include "base/bind.h"
32 #include "base/compiler_specific.h"
33 #include "base/logging.h"
34 #include "base/message_loop.h"
35 #include "base/string_util.h"
36 #include "base/time.h"
37 #include "net/base/net_errors.h"
38 #include "net/disk_cache/experiments.h"
39 #include "net/disk_cache/trace.h"
40 #include "net/disk_cache/v3/backend_impl_v3.h"
41 #include "net/disk_cache/v3/entry_impl_v3.h"
42 #include "net/disk_cache/v3/histogram_macros.h"
43 #include "net/disk_cache/v3/index_table.h"
44
45
46 using base::Time;
47 using base::TimeDelta;
48 using base::TimeTicks;
49
50 namespace {
51
52 const int kCleanUpMargin = 1024 * 1024;
53 const int kHighUse = 10; // Reuse count to be on the HIGH_USE list.
54 const int kTargetTime = 24 * 7; // Time to be evicted (hours since last use).
55 const int kMaxDelayedTrims = 60;
56
57 int LowWaterAdjust(int high_water) {
58 if (high_water < kCleanUpMargin)
59 return 0;
60
61 return high_water - kCleanUpMargin;
62 }
63
64 bool FallingBehind(int current_size, int max_size) {
65 return current_size > max_size - kCleanUpMargin * 20;
66 }
67
68 } // namespace
69
70 namespace disk_cache {
71
72 // The real initialization happens during Init(), init_ is the only member that
73 // has to be initialized here.
74 EvictionV3::EvictionV3()
75 : backend_(NULL),
76 index_(NULL),
77 header_(NULL),
78 init_(false),
79 cells_to_evict_(NULL),
80 ptr_factory_(this) {
81 }
82
83 EvictionV3::~EvictionV3() {
84 }
85
86 void EvictionV3::Init(BackendImplV3* backend) {
87 // We grab a bunch of info from the backend to make the code a little cleaner
88 // when we're actually doing work.
89 backend_ = backend;
90 index_ = &backend_->index_;
91 header_ = index_->header();
92 max_size_ = LowWaterAdjust(backend_->max_size_);
93 index_size_ = 1;//?
94 lru_ = backend->lru_eviction_;
95 first_trim_ = true;
96 trimming_ = false;
97 delay_trim_ = false;
98 trim_delays_ = 0;
99 init_ = true;
100 test_mode_ = false;
101 empty_ = false;
102 }
103
104 void EvictionV3::Stop() {
105 // It is possible for the backend initialization to fail, in which case this
106 // object was never initialized... and there is nothing to do.
107 if (!init_)
108 return;
109
110 // We want to stop further evictions, so let's pretend that we are busy from
111 // this point on.
112 DCHECK(!trimming_);//cannot do
113 trimming_ = true;
114 ptr_factory_.InvalidateWeakPtrs();
115 }
116
117 void EvictionV3::TrimCache() {
118 if (trimming_)
119 return;
120
121 if (!ShouldTrim())
122 return PostDelayedTrim();
123
124 Trace("*** Trim Cache ***");
125 trimming_ = true;
126
127 if (!TrimCacheImpl()) {
128 trimming_ = false;
129 Trace("*** Trim Cache end ***");
130 }
131 }
132
133 int EvictionV3::TrimAllCache(const net::CompletionCallback& callback) {
134 if (!callback_.is_null())
135 return net::ERR_FAILED;
136
137 empty_ = true;
138 trimming_ = true;
139 Trace("*** Trim All Cache ***");
140
141 if (!TrimCacheImpl())
142 return net::OK;
143
144 callback_ = callback;
145 return net::ERR_IO_PENDING;
146 }
147
148 void EvictionV3::OnOpenEntry(EntryImplV3* entry) {
149 if (lru_)
150 return;
151
152 EntryCell cell = index_->FindEntryCell(entry->GetHash(), entry->GetAddress());
153 DCHECK_NE(cell.GetGroup(), ENTRY_EVICTED);
154 int reuse_count = entry->GetReuseCounter();
155
156 if (reuse_count < 256) {
157 reuse_count++;
158 entry->SetReuseCounter(reuse_count);
159
160 // We may need to move this to a new list.
161 if (1 == reuse_count) {
162 DCHECK_EQ(cell.GetGroup(), ENTRY_NO_USE);
163 cell.SetGroup(ENTRY_LOW_USE);
164 } else if (kHighUse == reuse_count) {
165 DCHECK_EQ(cell.GetGroup(), ENTRY_LOW_USE);
166 cell.SetGroup(ENTRY_HIGH_USE);
167 }
168 if (reuse_count < 16) {
169 cell.SetReuse(reuse_count);
170 index_->Save(&cell);
171 }
172 }
173 }
174
175 void EvictionV3::OnCreateEntry(EntryImplV3* entry) {
176 EntryCell cell = index_->FindEntryCell(entry->GetHash(), entry->GetAddress());
177 DCHECK_EQ(cell.GetGroup(), ENTRY_NO_USE);
178 DCHECK(!entry->GetReuseCounter());
179 DCHECK(!entry->GetRefetchCounter());
180 }
181
182 void EvictionV3::OnResurrectEntry(EntryImplV3* entry) {
183 DCHECK(!lru_);
184 EntryCell cell = index_->FindEntryCell(entry->GetHash(), entry->GetAddress());
185 DCHECK_EQ(cell.GetGroup(), ENTRY_NO_USE);
186 int reuse_count = entry->GetReuseCounter();
187 int refetch_count = entry->GetRefetchCounter();
188
189 if (refetch_count < 256) {
190 refetch_count++;
191 entry->SetRefetchCounter(refetch_count);
192 }
193
194 if (refetch_count > kHighUse && reuse_count < kHighUse) {
195 reuse_count = kHighUse;
196 } else if (reuse_count < 256) {
197 reuse_count++;
198 }
199 entry->SetReuseCounter(reuse_count);
200
201 if (reuse_count >= kHighUse)
202 cell.SetGroup(ENTRY_HIGH_USE);
203 else
204 cell.SetGroup(ENTRY_LOW_USE);
205
206 if (reuse_count < 16)
207 cell.SetReuse(reuse_count);
208
209 index_->Save(&cell);
210 }
211
212 void EvictionV3::OnEvictEntryComplete() {
213 if (!test_mode_ && TrimCacheImpl())
214 return;
215
216 trimming_ = false;
217 Trace("*** Trim Cache end ***");
218
219 if (!callback_.is_null())
220 callback_.Run(net::OK);
221 }
222
223 void EvictionV3::SetTestMode() {
224 test_mode_ = true;
225 }
226
227 void EvictionV3::TrimDeletedList(bool empty) {
228 DCHECK(test_mode_ && !lru_);
229 TrimDeleted(empty);
230 }
231
232 // -----------------------------------------------------------------------
233
234 void EvictionV3::PostDelayedTrim() {
235 // Prevent posting multiple tasks.
236 if (delay_trim_)
237 return;
238 delay_trim_ = true;
239 trim_delays_++;
240 MessageLoop::current()->PostDelayedTask(FROM_HERE,
241 base::Bind(&EvictionV3::DelayedTrim, ptr_factory_.GetWeakPtr()),
242 base::TimeDelta::FromMilliseconds(1000));
243 }
244
245 void EvictionV3::DelayedTrim() {
246 delay_trim_ = false;
247 if (trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded())
248 return PostDelayedTrim();
249
250 TrimCache();
251 }
252
253 bool EvictionV3::ShouldTrim() {
254 if (!FallingBehind(header_->num_bytes, max_size_) &&
255 trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded()) {
256 return false;
257 }
258
259 UMA_HISTOGRAM_COUNTS("DiskCache.TrimDelays", trim_delays_);
260 trim_delays_ = 0;
261 return true;
262 }
263
264 bool EvictionV3::ShouldTrimDeleted() {
265 int index_load = header_->num_entries * 100 / index_size_;
266
267 // If the index is not loaded, the deleted list will tend to double the size
268 // of the other lists 3 lists (40% of the total). Otherwise, all lists will be
269 // about the same size.
270 int max_length = (index_load < 25) ? header_->num_entries * 2 / 5 :
271 header_->num_entries / 4;
272 return false;//(!test_mode_ && header_->lru.sizes[Rankings::DELETED] > max_len gth);
273 }
274
275 int EvictionV3::EvictEntry(uint32 hash, Addr address) {
276 if (empty_) {
277 EntryImplV3* entry = backend_->GetOpenEntry(address);
278 if (entry) {
279 entry->Doom();
280 entry->Close();
281 return net::OK;
282 }
283 }
284
285 if (backend_->EvictEntry(hash, address))
286 return net::ERR_IO_PENDING;
287
288 return net::ERR_FAILED;
289 }
290
291 bool EvictionV3::TrimCacheImpl() {
292 int target_size = empty_ ? 0 : max_size_;
293
294 if (!cells_to_evict_ || cells_to_evict_->cells.empty()) {
295 // See if we are done.
296 if (header_->num_bytes < target_size && !test_mode_)
297 return false;
298
299 EntryGroup group = ENTRY_RESERVED;
300 index_->GetOldest(&no_use_cells_, &low_use_cells_, &high_use_cells_);
301
302 if (CellIsOldEnough(no_use_cells_, 1))
303 group = ENTRY_NO_USE;
304 else if (CellIsOldEnough(low_use_cells_, 2))
305 group = ENTRY_LOW_USE;
306 else if (CellIsOldEnough(high_use_cells_, 4))
307 group = ENTRY_HIGH_USE;
308 else
309 group = SelectListByLength();
310
311 if (group == ENTRY_NO_USE) {
312 cells_to_evict_ = &no_use_cells_;
313 low_use_cells_.cells.clear();
314 high_use_cells_.cells.clear();
315 } else if (group == ENTRY_LOW_USE) {
316 cells_to_evict_ = &low_use_cells_;
317 no_use_cells_.cells.clear();
318 high_use_cells_.cells.clear();
319 } else if (group == ENTRY_HIGH_USE) {
320 cells_to_evict_ = &high_use_cells_;
321 no_use_cells_.cells.clear();
322 low_use_cells_.cells.clear();
323 } else {
324 return false;
325 }
326
327 if (first_trim_) {
328 first_trim_ = false;
329 if (backend_->ShouldReportAgain()) {
330 CACHE_UMA(AGE, "TrimAge",
331 index_->TimeFromTimestamp(cells_to_evict_->timestamp));
332 ReportListStats(no_use_cells_.timestamp, low_use_cells_.timestamp,
333 high_use_cells_.timestamp);
334 }
335
336 if (!(header_->flags & CACHE_EVICTED)) {
337 // This is the first entry that we have to evict, generate some noise.
338 backend_->FirstEviction();
339 }
340 }
341
342
343 DCHECK(!cells_to_evict_->cells.empty());
344 }
345
346 int rv = net::OK;
347 while (!cells_to_evict_->cells.empty()) {
348 rv = EvictEntry(cells_to_evict_->cells.back().hash,
349 cells_to_evict_->cells.back().address);
350 cells_to_evict_->cells.pop_back();
351 if (rv != net::ERR_FAILED) {
352 if (!empty_)
353 backend_->OnEvent(Stats::TRIM_ENTRY);
354 if (rv == net::ERR_IO_PENDING)
355 break;
356 }
357 }
358
359 if (test_mode_ || rv != net::ERR_IO_PENDING)
360 return false;
361
362 return true;
363 }
364
365 // This is a minimal implementation that just discards the oldest nodes.
366 // TODO(rvargas): Do something better here.
367 void EvictionV3::TrimDeleted(bool empty) {
368 Trace("*** Trim Deleted ***");
369 if (backend_->disabled_)
370 return;
371
372 /*TimeTicks start = TimeTicks::Now();
373 Rankings::ScopedRankingsBlock node(rankings_);
374 Rankings::ScopedRankingsBlock next(
375 rankings_, rankings_->GetPrev(node.get(), Rankings::DELETED));
376 int deleted_entries = 0;
377 while (next.get() &&
378 (empty || (deleted_entries < 20 &&
379 (TimeTicks::Now() - start).InMilliseconds() < 20))) {
380 node.reset(next.release());
381 next.reset(rankings_->GetPrev(node.get(), Rankings::DELETED));
382 if (RemoveDeletedNode(node.get()))
383 deleted_entries++;
384 if (test_mode_)
385 break;
386 }
387
388 if (deleted_entries && !empty && ShouldTrimDeleted()) {
389 MessageLoop::current()->PostTask(FROM_HERE,
390 base::Bind(&EvictionV3::TrimDeleted, ptr_factory_.GetWeakPtr(), false));
391 }
392
393 CACHE_UMA(AGE_MS, "TotalTrimDeletedTime", 0, start);
394 CACHE_UMA(COUNTS, "TrimDeletedItems", 0, deleted_entries);*/
395 Trace("*** Trim Deleted end ***");
396 return;
397 }
398
399 bool EvictionV3::CellIsOldEnough(const IndexIterator& iterator,
400 int multiplier) {
401 if (iterator.cells.empty())
402 return false;
403
404 if (lru_)
405 return true;
406
407 Time used = index_->TimeFromTimestamp(iterator.timestamp);
408
409 // If possible, we want to keep entries on each list at least kTargetTime
410 // hours. Each successive list on the enumeration has 2x the target time of
411 // the previous list.
412 return (backend_->GetTime() - used).InHours() > kTargetTime * multiplier;
413 }
414
415 EntryGroup EvictionV3::SelectListByLength() {
416 int data_entries = header_->num_entries - header_->num_evicted_entries;
417
418 // Start by having each list to be roughly the same size.
419 if (header_->num_no_use_entries > data_entries / 3) {
420 DCHECK(!no_use_cells_.cells.empty());
421 return ENTRY_NO_USE;
422 }
423
424 // Make sure that frequently used items are kept for a minimum time; we know
425 // that this entry is not older than its current target, but it must be at
426 // least older than the target for unused entries (kTargetTime), as long as we
427 // don't exhaust that list.
428
429 bool could_delete_no_used = header_->num_no_use_entries > data_entries / 10 ||
430 (!no_use_cells_.cells.empty() && empty_);
431
432 if (header_->num_low_use_entries > data_entries / 3 &&
433 (CellIsOldEnough(low_use_cells_, 1) || !could_delete_no_used)) {
434 DCHECK(!low_use_cells_.cells.empty());
435 return ENTRY_LOW_USE;
436 }
437
438 if (header_->num_high_use_entries > data_entries / 3 &&
439 (CellIsOldEnough(high_use_cells_, 1) || !could_delete_no_used)) {
440 DCHECK(!high_use_cells_.cells.empty());
441 return ENTRY_HIGH_USE;
442 }
443
444 if (could_delete_no_used)
445 return ENTRY_NO_USE;
446
447 if (!low_use_cells_.cells.empty() && empty_)
448 return ENTRY_LOW_USE;
449
450 if (!high_use_cells_.cells.empty() && empty_)
451 return ENTRY_HIGH_USE;
452
453 return ENTRY_RESERVED;
454 }
455
456 void EvictionV3::ReportListStats(int time1, int time2, int time3) {
457 if (lru_)
458 return;
459
460 if (time1)
461 CACHE_UMA(AGE, "NoUseAge", index_->TimeFromTimestamp(time1));
462
463 if (time2)
464 CACHE_UMA(AGE, "LowUseAge", index_->TimeFromTimestamp(time2));
465
466 if (time3)
467 CACHE_UMA(AGE, "HighUseAge", index_->TimeFromTimestamp(time3));
468 }
469
470 } // namespace disk_cache
OLDNEW
« no previous file with comments | « net/disk_cache/v3/eviction_v3.h ('k') | net/disk_cache/v3/index_table.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698