Chromium Code Reviews| 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 |