OLD | NEW |
---|---|
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 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 #include "cc/resources/resource_pool.h" | 5 #include "cc/resources/resource_pool.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/format_macros.h" | 9 #include "base/format_macros.h" |
10 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
11 #include "base/thread_task_runner_handle.h" | 11 #include "base/thread_task_runner_handle.h" |
12 #include "base/trace_event/memory_dump_manager.h" | 12 #include "base/trace_event/memory_dump_manager.h" |
13 #include "cc/resources/resource_provider.h" | 13 #include "cc/resources/resource_provider.h" |
14 #include "cc/resources/resource_util.h" | 14 #include "cc/resources/resource_util.h" |
15 #include "cc/resources/scoped_resource.h" | 15 #include "cc/resources/scoped_resource.h" |
16 | 16 |
17 namespace cc { | 17 namespace cc { |
18 | 18 |
19 namespace { | |
reveman
2015/08/20 21:43:37
nit: move this blank line below l.19 instead
ericrk
2015/08/21 12:48:22
I *think* I did what you meant? Tried to match oth
| |
20 // Delay before a resource is considered expired. | |
21 const int kResourceExpirationDelayMs = 1000; | |
22 } // namespace | |
reveman
2015/08/20 21:43:37
nit: blank line above this
ericrk
2015/08/21 12:48:22
Done.
| |
23 | |
19 void ResourcePool::PoolResource::OnMemoryDump( | 24 void ResourcePool::PoolResource::OnMemoryDump( |
20 base::trace_event::ProcessMemoryDump* pmd, | 25 base::trace_event::ProcessMemoryDump* pmd, |
21 const ResourceProvider* resource_provider, | 26 const ResourceProvider* resource_provider, |
22 bool is_free) const { | 27 bool is_free) const { |
23 // Resource IDs are not process-unique, so log with the ResourceProvider's | 28 // Resource IDs are not process-unique, so log with the ResourceProvider's |
24 // unique id. | 29 // unique id. |
25 std::string parent_node = | 30 std::string parent_node = |
26 base::StringPrintf("cc/resource_memory/resource_provider_%d/resource_%d", | 31 base::StringPrintf("cc/resource_memory/resource_provider_%d/resource_%d", |
27 resource_provider->tracing_id(), id()); | 32 resource_provider->tracing_id(), id()); |
28 | 33 |
(...skipping 10 matching lines...) Expand all Loading... | |
39 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, | 44 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
40 base::trace_event::MemoryAllocatorDump::kUnitsBytes, | 45 base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
41 total_bytes); | 46 total_bytes); |
42 | 47 |
43 if (is_free) { | 48 if (is_free) { |
44 dump->AddScalar("free_size", | 49 dump->AddScalar("free_size", |
45 base::trace_event::MemoryAllocatorDump::kUnitsBytes, | 50 base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
46 total_bytes); | 51 total_bytes); |
47 } | 52 } |
48 } | 53 } |
54 | |
49 ResourcePool::ResourcePool(ResourceProvider* resource_provider) | 55 ResourcePool::ResourcePool(ResourceProvider* resource_provider) |
50 : resource_provider_(resource_provider), | 56 : ResourcePool(resource_provider, 0) {} |
51 target_(0), | |
52 max_memory_usage_bytes_(0), | |
53 max_unused_memory_usage_bytes_(0), | |
54 max_resource_count_(0), | |
55 memory_usage_bytes_(0), | |
56 unused_memory_usage_bytes_(0), | |
57 resource_count_(0) { | |
58 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( | |
59 this, base::ThreadTaskRunnerHandle::Get()); | |
60 } | |
61 | 57 |
62 ResourcePool::ResourcePool(ResourceProvider* resource_provider, GLenum target) | 58 ResourcePool::ResourcePool(ResourceProvider* resource_provider, GLenum target) |
63 : resource_provider_(resource_provider), | 59 : resource_provider_(resource_provider), |
64 target_(target), | 60 target_(target), |
65 max_memory_usage_bytes_(0), | 61 max_memory_usage_bytes_(0), |
66 max_unused_memory_usage_bytes_(0), | 62 max_unused_memory_usage_bytes_(0), |
67 max_resource_count_(0), | 63 max_resource_count_(0), |
68 memory_usage_bytes_(0), | 64 memory_usage_bytes_(0), |
69 unused_memory_usage_bytes_(0), | 65 unused_memory_usage_bytes_(0), |
70 resource_count_(0) { | 66 resource_count_(0), |
71 DCHECK_NE(0u, target); | 67 evict_expired_resources_pending_(false), |
68 resource_expiration_delay_( | |
69 base::TimeDelta::FromMilliseconds(kResourceExpirationDelayMs)), | |
70 weak_ptr_factory_(this) { | |
71 evict_expired_resources_callback_ = base::Bind( | |
reveman
2015/08/20 21:43:37
Unlike one-copy where worker threads are in play,
ericrk
2015/08/21 12:48:22
ok, makes sense.
| |
72 &ResourcePool::EvictExpiredResources, weak_ptr_factory_.GetWeakPtr()); | |
72 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( | 73 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
73 this, base::ThreadTaskRunnerHandle::Get()); | 74 this, base::ThreadTaskRunnerHandle::Get()); |
74 } | 75 } |
75 | 76 |
76 ResourcePool::~ResourcePool() { | 77 ResourcePool::~ResourcePool() { |
77 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( | 78 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
78 this); | 79 this); |
79 | 80 |
80 DCHECK_EQ(0u, in_use_resources_.size()); | 81 DCHECK_EQ(0u, in_use_resources_.size()); |
81 | 82 |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
147 resource->size(), resource->format()); | 148 resource->size(), resource->format()); |
148 return resource; | 149 return resource; |
149 } | 150 } |
150 | 151 |
151 void ResourcePool::ReleaseResource(Resource* resource, uint64_t content_id) { | 152 void ResourcePool::ReleaseResource(Resource* resource, uint64_t content_id) { |
152 auto it = in_use_resources_.find(resource->id()); | 153 auto it = in_use_resources_.find(resource->id()); |
153 DCHECK(it != in_use_resources_.end()); | 154 DCHECK(it != in_use_resources_.end()); |
154 | 155 |
155 PoolResource* pool_resource = it->second; | 156 PoolResource* pool_resource = it->second; |
156 pool_resource->set_content_id(content_id); | 157 pool_resource->set_content_id(content_id); |
158 pool_resource->set_last_usage(base::TimeTicks::Now()); | |
reveman
2015/08/20 21:43:37
Can we avoid calling Now() two times in this case.
ericrk
2015/08/21 12:48:22
Ok, refactored so we now only call Now() once in t
| |
157 | 159 |
158 // Transfer resource to |busy_resources_|. | 160 // Transfer resource to |busy_resources_|. |
159 busy_resources_.push_back(in_use_resources_.take_and_erase(it)); | 161 busy_resources_.push_back(in_use_resources_.take_and_erase(it)); |
162 | |
163 // Now that we have evictable resources, schedule cleanup if necessary. | |
164 ScheduleEvictExpiredResources(); | |
160 } | 165 } |
161 | 166 |
162 void ResourcePool::SetResourceUsageLimits(size_t max_memory_usage_bytes, | 167 void ResourcePool::SetResourceUsageLimits(size_t max_memory_usage_bytes, |
163 size_t max_unused_memory_usage_bytes, | 168 size_t max_unused_memory_usage_bytes, |
164 size_t max_resource_count) { | 169 size_t max_resource_count) { |
165 max_memory_usage_bytes_ = max_memory_usage_bytes; | 170 max_memory_usage_bytes_ = max_memory_usage_bytes; |
166 max_unused_memory_usage_bytes_ = max_unused_memory_usage_bytes; | 171 max_unused_memory_usage_bytes_ = max_unused_memory_usage_bytes; |
167 max_resource_count_ = max_resource_count; | 172 max_resource_count_ = max_resource_count; |
168 | 173 |
169 ReduceResourceUsage(); | 174 ReduceResourceUsage(); |
170 } | 175 } |
171 | 176 |
172 void ResourcePool::ReduceResourceUsage() { | 177 void ResourcePool::ReduceResourceUsage() { |
173 while (!unused_resources_.empty()) { | 178 while (!unused_resources_.empty()) { |
174 if (!ResourceUsageTooHigh()) | 179 if (!ResourceUsageTooHigh()) |
175 break; | 180 break; |
176 | 181 |
177 // LRU eviction pattern. Most recently used might be blocked by | 182 // LRU eviction pattern. Most recently used might be blocked by |
178 // a read lock fence but it's still better to evict the least | 183 // a read lock fence but it's still better to evict the least |
179 // recently used as it prevents a resource that is hard to reuse | 184 // recently used as it prevents a resource that is hard to reuse |
180 // because of unique size from being kept around. Resources that | 185 // because of unique size from being kept around. Resources that |
181 // can't be locked for write might also not be truly free-able. | 186 // can't be locked for write might also not be truly free-able. |
182 // We can free the resource here but it doesn't mean that the | 187 // We can free the resource here but it doesn't mean that the |
183 // memory is necessarily returned to the OS. | 188 // memory is necessarily returned to the OS. |
184 scoped_ptr<PoolResource> resource = unused_resources_.take_front(); | 189 DeleteUnusedResource(unused_resources_.take_front()); |
185 unused_memory_usage_bytes_ -= ResourceUtil::UncheckedSizeInBytes<size_t>( | |
186 resource->size(), resource->format()); | |
187 DeleteResource(resource.Pass()); | |
188 } | 190 } |
189 } | 191 } |
190 | 192 |
191 bool ResourcePool::ResourceUsageTooHigh() { | 193 bool ResourcePool::ResourceUsageTooHigh() { |
192 if (resource_count_ > max_resource_count_) | 194 if (resource_count_ > max_resource_count_) |
193 return true; | 195 return true; |
194 if (memory_usage_bytes_ > max_memory_usage_bytes_) | 196 if (memory_usage_bytes_ > max_memory_usage_bytes_) |
195 return true; | 197 return true; |
196 if (unused_memory_usage_bytes_ > max_unused_memory_usage_bytes_) | 198 if (unused_memory_usage_bytes_ > max_unused_memory_usage_bytes_) |
197 return true; | 199 return true; |
198 return false; | 200 return false; |
199 } | 201 } |
200 | 202 |
201 void ResourcePool::DeleteResource(scoped_ptr<PoolResource> resource) { | 203 void ResourcePool::DeleteResource(scoped_ptr<PoolResource> resource) { |
202 size_t resource_bytes = ResourceUtil::UncheckedSizeInBytes<size_t>( | 204 size_t resource_bytes = ResourceUtil::UncheckedSizeInBytes<size_t>( |
203 resource->size(), resource->format()); | 205 resource->size(), resource->format()); |
204 memory_usage_bytes_ -= resource_bytes; | 206 memory_usage_bytes_ -= resource_bytes; |
205 --resource_count_; | 207 --resource_count_; |
206 } | 208 } |
207 | 209 |
210 void ResourcePool::DeleteUnusedResource(scoped_ptr<PoolResource> resource) { | |
211 unused_memory_usage_bytes_ -= ResourceUtil::UncheckedSizeInBytes<size_t>( | |
212 resource->size(), resource->format()); | |
213 DeleteResource(resource.Pass()); | |
214 } | |
215 | |
208 void ResourcePool::CheckBusyResources() { | 216 void ResourcePool::CheckBusyResources() { |
209 for (size_t i = 0; i < busy_resources_.size();) { | 217 for (size_t i = 0; i < busy_resources_.size();) { |
210 ResourceDeque::iterator it(busy_resources_.begin() + i); | 218 ResourceDeque::iterator it(busy_resources_.begin() + i); |
211 PoolResource* resource = *it; | 219 PoolResource* resource = *it; |
212 | 220 |
213 if (resource_provider_->CanLockForWrite(resource->id())) { | 221 if (resource_provider_->CanLockForWrite(resource->id())) { |
214 DidFinishUsingResource(busy_resources_.take(it)); | 222 DidFinishUsingResource(busy_resources_.take(it)); |
215 } else if (resource_provider_->IsLost(resource->id())) { | 223 } else if (resource_provider_->IsLost(resource->id())) { |
216 // Remove lost resources from pool. | 224 // Remove lost resources from pool. |
217 DeleteResource(busy_resources_.take(it)); | 225 DeleteResource(busy_resources_.take(it)); |
218 } else { | 226 } else { |
219 ++i; | 227 ++i; |
220 } | 228 } |
221 } | 229 } |
222 } | 230 } |
223 | 231 |
224 void ResourcePool::DidFinishUsingResource(scoped_ptr<PoolResource> resource) { | 232 void ResourcePool::DidFinishUsingResource(scoped_ptr<PoolResource> resource) { |
225 unused_memory_usage_bytes_ += ResourceUtil::UncheckedSizeInBytes<size_t>( | 233 unused_memory_usage_bytes_ += ResourceUtil::UncheckedSizeInBytes<size_t>( |
226 resource->size(), resource->format()); | 234 resource->size(), resource->format()); |
227 unused_resources_.push_back(resource.Pass()); | 235 unused_resources_.push_back(resource.Pass()); |
228 } | 236 } |
229 | 237 |
238 void ResourcePool::ScheduleEvictExpiredResources() { | |
239 if (evict_expired_resources_pending_) | |
240 return; | |
241 | |
242 if (!HasEvictableResources()) | |
243 return; | |
244 | |
245 evict_expired_resources_pending_ = true; | |
246 | |
247 // Schedule a call to EvictExpiredResources at the time when the LRU buffer | |
248 // expires. | |
249 base::TimeTicks reduce_resource_usage_time = | |
250 GetUsageTimeForLRUResource() + resource_expiration_delay_; | |
251 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
reveman
2015/08/20 21:43:37
Should this task runner be passed to the ctor inst
ericrk
2015/08/21 12:48:22
Sure, we can do that.
| |
252 FROM_HERE, evict_expired_resources_callback_, | |
253 reduce_resource_usage_time - base::TimeTicks::Now()); | |
254 } | |
255 | |
256 void ResourcePool::EvictExpiredResources() { | |
257 evict_expired_resources_pending_ = false; | |
258 | |
259 if (!HasEvictableResources()) | |
260 return; | |
261 | |
262 EvictResourcesNotUsedSince(base::TimeTicks::Now() - | |
263 resource_expiration_delay_); | |
264 | |
265 // Re-schedule this function if necessary. | |
266 ScheduleEvictExpiredResources(); | |
267 } | |
268 | |
269 void ResourcePool::EvictResourcesNotUsedSince(base::TimeTicks time_limit) { | |
270 while (!unused_resources_.empty()) { | |
271 // |unused_resources_| is not strictly ordered with regards to last_usage, | |
272 // as this may not exactly line up with the time a resource became non-busy. | |
273 // However, this should be roughly ordered, and will only introduce slight | |
274 // delays in freeing expired resources. | |
275 if (unused_resources_.front()->last_usage() > time_limit) | |
276 return; | |
277 | |
278 DeleteUnusedResource(unused_resources_.take_front()); | |
279 } | |
280 | |
281 // Also free busy resources older than the delay. With a sufficiently large | |
282 // delay, such as the 1 second used here, any "busy" resources which have | |
283 // expired are not likely to be busy. Additionally, freeing a "busy" resource | |
284 // has no downside other than incorrect accounting. | |
285 while (!busy_resources_.empty()) { | |
286 if (busy_resources_.front()->last_usage() > time_limit) | |
287 return; | |
288 | |
289 DeleteResource(busy_resources_.take_front()); | |
290 } | |
291 } | |
292 | |
293 bool ResourcePool::HasEvictableResources() const { | |
294 return !unused_resources_.empty() || !busy_resources_.empty(); | |
295 } | |
296 | |
297 base::TimeTicks ResourcePool::GetUsageTimeForLRUResource() const { | |
298 if (!unused_resources_.empty()) { | |
299 return unused_resources_.front()->last_usage(); | |
300 } | |
301 | |
302 if (!busy_resources_.empty()) { | |
reveman
2015/08/20 21:43:37
DCHECK(!busy_resources_.empty()) instead as we sho
ericrk
2015/08/21 12:48:22
sounds good
| |
303 return busy_resources_.front()->last_usage(); | |
304 } | |
305 | |
306 return base::TimeTicks(); | |
307 } | |
308 | |
230 bool ResourcePool::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, | 309 bool ResourcePool::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, |
231 base::trace_event::ProcessMemoryDump* pmd) { | 310 base::trace_event::ProcessMemoryDump* pmd) { |
232 for (const auto& resource : unused_resources_) { | 311 for (const auto& resource : unused_resources_) { |
233 resource->OnMemoryDump(pmd, resource_provider_, true /* is_free */); | 312 resource->OnMemoryDump(pmd, resource_provider_, true /* is_free */); |
234 } | 313 } |
235 for (const auto& resource : busy_resources_) { | 314 for (const auto& resource : busy_resources_) { |
236 resource->OnMemoryDump(pmd, resource_provider_, false /* is_free */); | 315 resource->OnMemoryDump(pmd, resource_provider_, false /* is_free */); |
237 } | 316 } |
238 for (const auto& entry : in_use_resources_) { | 317 for (const auto& entry : in_use_resources_) { |
239 entry.second->OnMemoryDump(pmd, resource_provider_, false /* is_free */); | 318 entry.second->OnMemoryDump(pmd, resource_provider_, false /* is_free */); |
240 } | 319 } |
241 return true; | 320 return true; |
242 } | 321 } |
243 | 322 |
244 } // namespace cc | 323 } // namespace cc |
OLD | NEW |