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

Side by Side Diff: net/disk_cache/eviction.cc

Issue 27345: New disk cache eviction algorithm. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 9 months 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/eviction.h ('k') | net/disk_cache/rankings.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 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 ads 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
5 #include "net/disk_cache/eviction.h" 29 #include "net/disk_cache/eviction.h"
6 30
7 #include "base/logging.h" 31 #include "base/logging.h"
8 #include "base/message_loop.h" 32 #include "base/message_loop.h"
9 #include "base/string_util.h" 33 #include "base/string_util.h"
10 #include "base/time.h" 34 #include "base/time.h"
11 #include "net/disk_cache/backend_impl.h" 35 #include "net/disk_cache/backend_impl.h"
12 #include "net/disk_cache/entry_impl.h" 36 #include "net/disk_cache/entry_impl.h"
13 #include "net/disk_cache/trace.h" 37 #include "net/disk_cache/trace.h"
14 38
15 using base::Time; 39 using base::Time;
16 40
17 namespace { 41 namespace {
18 42
19 const int kCleanUpMargin = 1024 * 1024; 43 const int kCleanUpMargin = 1024 * 1024;
44 const int kHighUse = 10; // Reuse count to be on the HIGH_USE list.
45 const int kTargetTime = 24 * 7; // Time to be evicted (hours since last use).
20 46
21 int LowWaterAdjust(int high_water) { 47 int LowWaterAdjust(int high_water) {
22 if (high_water < kCleanUpMargin) 48 if (high_water < kCleanUpMargin)
23 return 0; 49 return 0;
24 50
25 return high_water - kCleanUpMargin; 51 return high_water - kCleanUpMargin;
26 } 52 }
27 53
28 } // namespace 54 } // namespace
29 55
30 namespace disk_cache { 56 namespace disk_cache {
31 57
32 void Eviction::Init(BackendImpl* backend) { 58 void Eviction::Init(BackendImpl* backend) {
33 // We grab a bunch of info from the backend to make the code a little cleaner 59 // We grab a bunch of info from the backend to make the code a little cleaner
34 // when we're actually doing work. 60 // when we're actually doing work.
35 backend_ = backend; 61 backend_ = backend;
36 rankings_ = &backend->rankings_; 62 rankings_ = &backend->rankings_;
37 header_ = &backend_->data_->header; 63 header_ = &backend_->data_->header;
38 max_size_ = LowWaterAdjust(backend_->max_size_); 64 max_size_ = LowWaterAdjust(backend_->max_size_);
65 new_eviction_ = backend->new_eviction_;
39 } 66 }
40 67
41 void Eviction::TrimCache(bool empty) { 68 void Eviction::TrimCache(bool empty) {
69 if (new_eviction_)
70 return TrimCacheV2(empty);
71
42 Trace("*** Trim Cache ***"); 72 Trace("*** Trim Cache ***");
43 if (backend_->disabled_) 73 if (backend_->disabled_)
44 return; 74 return;
45 75
46 Time start = Time::Now(); 76 Time start = Time::Now();
47 Rankings::ScopedRankingsBlock node(rankings_); 77 Rankings::ScopedRankingsBlock node(rankings_);
48 Rankings::ScopedRankingsBlock next(rankings_, 78 Rankings::ScopedRankingsBlock next(rankings_,
49 rankings_->GetPrev(node.get(), Rankings::NO_USE)); 79 rankings_->GetPrev(node.get(), Rankings::NO_USE));
50 DCHECK(next.get()); 80 DCHECK(next.get());
51 int target_size = empty ? 0 : max_size_; 81 int target_size = empty ? 0 : max_size_;
52 int deleted = 0; 82 int deleted = 0;
53 while (header_->num_bytes > target_size && next.get()) { 83 while (header_->num_bytes > target_size && next.get()) {
54 node.reset(next.release()); 84 node.reset(next.release());
55 next.reset(rankings_->GetPrev(node.get(), Rankings::NO_USE)); 85 next.reset(rankings_->GetPrev(node.get(), Rankings::NO_USE));
56 if (!node->Data()->pointer || empty) { 86 if (!node->Data()->pointer || empty) {
57 // This entry is not being used by anybody. 87 // This entry is not being used by anybody.
58 EntryImpl* entry; 88 if (!EvictEntry(node.get(), empty))
59 bool dirty;
60 if (backend_->NewEntry(Addr(node->Data()->contents), &entry, &dirty)) {
61 Trace("NewEntry failed on Trim 0x%x", node->address().value());
62 continue; 89 continue;
63 }
64 90
65 if (node->Data()->pointer) {
66 entry = EntryImpl::Update(entry);
67 }
68 ReportTrimTimes(entry);
69 entry->Doom();
70 entry->Release();
71 if (!empty) 91 if (!empty)
72 backend_->OnEvent(Stats::TRIM_ENTRY); 92 backend_->OnEvent(Stats::TRIM_ENTRY);
73 if (++deleted == 4 && !empty) { 93 if (++deleted == 4 && !empty) {
74 #if defined(OS_WIN) 94 #if defined(OS_WIN)
75 MessageLoop::current()->PostTask(FROM_HERE, 95 MessageLoop::current()->PostTask(FROM_HERE,
76 factory_.NewRunnableMethod(&Eviction::TrimCache, false)); 96 factory_.NewRunnableMethod(&Eviction::TrimCache, false));
77 break; 97 break;
78 #endif 98 #endif
79 } 99 }
80 } 100 }
81 } 101 }
82 102
83 UMA_HISTOGRAM_TIMES("DiskCache.TotalTrimTime", Time::Now() - start); 103 UMA_HISTOGRAM_TIMES("DiskCache.TotalTrimTime", Time::Now() - start);
84 Trace("*** Trim Cache end ***"); 104 Trace("*** Trim Cache end ***");
85 return; 105 return;
86 } 106 }
87 107
88 void Eviction::UpdateRank(EntryImpl* entry, bool modified) { 108 void Eviction::UpdateRank(EntryImpl* entry, bool modified) {
109 if (new_eviction_)
110 return UpdateRankV2(entry, modified);
111
89 rankings_->UpdateRank(entry->rankings(), modified, GetListForEntry(entry)); 112 rankings_->UpdateRank(entry->rankings(), modified, GetListForEntry(entry));
90 } 113 }
91 114
92 void Eviction::OnOpenEntry(EntryImpl* entry) { 115 void Eviction::OnOpenEntry(EntryImpl* entry) {
116 if (new_eviction_)
117 return OnOpenEntryV2(entry);
93 } 118 }
94 119
95 void Eviction::OnCreateEntry(EntryImpl* entry) { 120 void Eviction::OnCreateEntry(EntryImpl* entry) {
121 if (new_eviction_)
122 return OnCreateEntryV2(entry);
123
96 rankings_->Insert(entry->rankings(), true, GetListForEntry(entry)); 124 rankings_->Insert(entry->rankings(), true, GetListForEntry(entry));
97 } 125 }
98 126
99 void Eviction::OnDoomEntry(EntryImpl* entry) { 127 void Eviction::OnDoomEntry(EntryImpl* entry) {
128 if (new_eviction_)
129 return OnDoomEntryV2(entry);
130
100 rankings_->Remove(entry->rankings(), GetListForEntry(entry)); 131 rankings_->Remove(entry->rankings(), GetListForEntry(entry));
101 } 132 }
102 133
103 void Eviction::OnDestroyEntry(EntryImpl* entry) { 134 void Eviction::OnDestroyEntry(EntryImpl* entry) {
135 if (new_eviction_)
136 return OnDestroyEntryV2(entry);
104 } 137 }
105 138
106 void Eviction::ReportTrimTimes(EntryImpl* entry) { 139 void Eviction::ReportTrimTimes(EntryImpl* entry) {
107 static bool first_time = true; 140 static bool first_time = true;
108 if (first_time) { 141 if (first_time) {
109 first_time = false; 142 first_time = false;
110 std::string name(StringPrintf("DiskCache.TrimAge_%d", 143 std::string name(StringPrintf("DiskCache.TrimAge_%d",
111 header_->experiment)); 144 header_->experiment));
112 static Histogram counter(name.c_str(), 1, 10000, 50); 145 static Histogram counter(name.c_str(), 1, 10000, 50);
113 counter.SetFlags(kUmaTargetedHistogramFlag); 146 counter.SetFlags(kUmaTargetedHistogramFlag);
114 counter.Add((Time::Now() - entry->GetLastUsed()).InHours()); 147 counter.Add((Time::Now() - entry->GetLastUsed()).InHours());
115 } 148 }
116 } 149 }
117 150
118 Rankings::List Eviction::GetListForEntry(EntryImpl* entry) { 151 Rankings::List Eviction::GetListForEntry(EntryImpl* entry) {
119 return Rankings::NO_USE; 152 return Rankings::NO_USE;
120 } 153 }
121 154
155 bool Eviction::EvictEntry(CacheRankingsBlock* node, bool empty) {
156 EntryImpl* entry;
157 bool dirty;
158 if (backend_->NewEntry(Addr(node->Data()->contents), &entry, &dirty)) {
159 Trace("NewEntry failed on Trim 0x%x", node->address().value());
160 return false;
161 }
162
163 if (node->Data()->pointer) {
164 entry = EntryImpl::Update(entry);
165 }
166 ReportTrimTimes(entry);
167 if (empty || !new_eviction_) {
168 entry->Doom();
169 } else {
170 entry->DeleteEntryData(false);
171 EntryStore* info = entry->entry()->Data();
172 DCHECK(ENTRY_NORMAL == info->state);
173
174 rankings_->Remove(entry->rankings(), GetListForEntryV2(entry));
175 info->state = ENTRY_EVICTED;
176 entry->entry()->Store();
177 rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
178 backend_->OnEvent(Stats::TRIM_ENTRY);
179 }
180 entry->Release();
181
182 return true;
183 }
184
185 // -----------------------------------------------------------------------
186
187 void Eviction::TrimCacheV2(bool empty) {
188 Trace("*** Trim Cache ***");
189 if (backend_->disabled_)
190 return;
191
192 Time start = Time::Now();
193
194 const int kListsToSearch = 3;
195 Rankings::ScopedRankingsBlock next[kListsToSearch];
196 int list = Rankings::LAST_ELEMENT;
197
198 // Get a node from each list.
199 for (int i = 0; i < kListsToSearch; i++) {
200 bool done = false;
201 next[i].set_rankings(rankings_);
202 if (done)
203 continue;
204 next[i].reset(rankings_->GetPrev(NULL, static_cast<Rankings::List>(i)));
205 if (!empty && NodeIsOldEnough(next[i].get(), i)) {
206 list = static_cast<Rankings::List>(i);
207 done = true;
208 }
209 }
210
211 // If we are not meeting the time targets lets move on to list length.
212 if (!empty && Rankings::LAST_ELEMENT == list)
213 list = SelectListByLenght();
214
215 if (empty)
216 list = 0;
217
218 Rankings::ScopedRankingsBlock node(rankings_);
219
220 int target_size = empty ? 0 : max_size_;
221 int deleted = 0;
222 for (; list < kListsToSearch; list++) {
223 while (header_->num_bytes > target_size && next[list].get()) {
224 node.reset(next[list].release());
225 next[list].reset(rankings_->GetPrev(node.get(),
226 static_cast<Rankings::List>(list)));
227 if (!node->Data()->pointer || empty) {
228 // This entry is not being used by anybody.
229 if (!EvictEntry(node.get(), empty))
230 continue;
231
232 if (++deleted == 4 && !empty) {
233 MessageLoop::current()->PostTask(FROM_HERE,
234 factory_.NewRunnableMethod(&Eviction::TrimCache, false));
235 break;
236 }
237 }
238 }
239 if (!empty)
240 list = kListsToSearch;
241 }
242
243 if (empty || header_->lru.sizes[Rankings::DELETED] > header_->num_entries / 4)
244 MessageLoop::current()->PostTask(FROM_HERE,
245 factory_.NewRunnableMethod(&Eviction::TrimDeleted, empty));
246
247 UMA_HISTOGRAM_TIMES("DiskCache.TotalTrimTime", Time::Now() - start);
248 Trace("*** Trim Cache end ***");
249 return;
250 }
251
252 void Eviction::UpdateRankV2(EntryImpl* entry, bool modified) {
253 rankings_->UpdateRank(entry->rankings(), modified, GetListForEntryV2(entry));
254 }
255
256 void Eviction::OnOpenEntryV2(EntryImpl* entry) {
257 EntryStore* info = entry->entry()->Data();
258 DCHECK(ENTRY_NORMAL == info->state);
259
260 if (info->reuse_count < kint32max) {
261 info->reuse_count++;
262 entry->entry()->set_modified();
263
264 // We may need to move this to a new list.
265 if (1 == info->reuse_count) {
266 rankings_->Remove(entry->rankings(), Rankings::NO_USE);
267 rankings_->Insert(entry->rankings(), false, Rankings::LOW_USE);
268 entry->entry()->Store();
269 } else if (kHighUse == info->reuse_count) {
270 rankings_->Remove(entry->rankings(), Rankings::LOW_USE);
271 rankings_->Insert(entry->rankings(), false, Rankings::HIGH_USE);
272 entry->entry()->Store();
273 }
274 }
275 }
276
277 void Eviction::OnCreateEntryV2(EntryImpl* entry) {
278 EntryStore* info = entry->entry()->Data();
279 switch (info->state) {
280 case ENTRY_NORMAL: {
281 DCHECK(!info->reuse_count);
282 DCHECK(!info->refetch_count);
283 break;
284 };
285 case ENTRY_EVICTED: {
286 if (info->refetch_count < kint32max)
287 info->refetch_count++;
288
289 if (info->refetch_count > kHighUse && info->reuse_count < kHighUse) {
290 info->reuse_count = kHighUse;
291 } else {
292 info->reuse_count++;
293 }
294 info->state = ENTRY_NORMAL;
295 entry->entry()->Store();
296 rankings_->Remove(entry->rankings(), Rankings::DELETED);
297 break;
298 };
299 default:
300 NOTREACHED();
301 }
302
303 rankings_->Insert(entry->rankings(), true, GetListForEntryV2(entry));
304 }
305
306 void Eviction::OnDoomEntryV2(EntryImpl* entry) {
307 EntryStore* info = entry->entry()->Data();
308 if (ENTRY_NORMAL != info->state)
309 return;
310
311 rankings_->Remove(entry->rankings(), GetListForEntryV2(entry));
312
313 info->state = ENTRY_DOOMED;
314 entry->entry()->Store();
315 rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
316 }
317
318 void Eviction::OnDestroyEntryV2(EntryImpl* entry) {
319 rankings_->Remove(entry->rankings(), Rankings::DELETED);
320 }
321
322 Rankings::List Eviction::GetListForEntryV2(EntryImpl* entry) {
323 EntryStore* info = entry->entry()->Data();
324 DCHECK(ENTRY_NORMAL == info->state);
325
326 if (!info->reuse_count)
327 return Rankings::NO_USE;
328
329 if (info->reuse_count < kHighUse)
330 return Rankings::LOW_USE;
331
332 return Rankings::HIGH_USE;
333 }
334
335 void Eviction::TrimDeleted(bool empty) {
336 }
337
338 bool Eviction::NodeIsOldEnough(CacheRankingsBlock* node, int list) {
339 if (!node)
340 return false;
341
342 // If possible, we want to keep entries on each list at least kTargetTime
343 // hours. Each successive list on the enumeration has 2x the target time of
344 // the previous list.
345 Time used = Time::FromInternalValue(node->Data()->last_used);
346 int multiplier = 1 << list;
347 return (Time::Now() - used).InHours() > kTargetTime * multiplier;
348 }
349
350 int Eviction::SelectListByLenght() {
351 // Start by having each list to be roughly the same size.
352 if (header_->lru.sizes[0] > header_->num_entries / 4)
353 return 0;
354 if (header_->lru.sizes[1] > header_->num_entries / 4)
355 return 1;
356 return 2;
357 }
358
122 } // namespace disk_cache 359 } // namespace disk_cache
OLDNEW
« no previous file with comments | « net/disk_cache/eviction.h ('k') | net/disk_cache/rankings.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698