OLD | NEW |
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 |
OLD | NEW |