blob: 249ab9bf3f692653bd646e1da42dcbea2b1cdef6 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/command_buffer/service/memory_cache.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "gpu/command_buffer/service/service_utils.h"
namespace gpu {
MemoryCacheEntry::MemoryCacheEntry(std::string_view key,
base::span<const uint8_t> data)
: key_(key), data_(base::HeapArray<uint8_t>::CopiedFrom(data)) {}
MemoryCacheEntry::MemoryCacheEntry(std::string_view key,
base::HeapArray<uint8_t> data)
: key_(key), data_(std::move(data)) {}
MemoryCacheEntry::~MemoryCacheEntry() = default;
std::string_view MemoryCacheEntry::Key() const {
return key_;
}
size_t MemoryCacheEntry::TotalSize() const {
return key_.length() + data_.size();
}
size_t MemoryCacheEntry::DataSize() const {
return data_.size();
}
size_t MemoryCacheEntry::ReadData(void* value_out, size_t value_size) const {
// First handle "peek" case where use is trying to get the size of the entry.
if (value_out == nullptr && value_size == 0) {
return DataSize();
}
// Otherwise, verify that the size that is being copied out is identical.
DCHECK(value_size == DataSize());
std::copy(data_.begin(), data_.end(), static_cast<uint8_t*>(value_out));
return value_size;
}
base::span<const uint8_t> MemoryCacheEntry::Data() const {
return data_;
}
bool operator<(const scoped_refptr<MemoryCacheEntry>& lhs,
const scoped_refptr<MemoryCacheEntry>& rhs) {
return lhs->Key() < rhs->Key();
}
bool operator<(const scoped_refptr<MemoryCacheEntry>& lhs,
std::string_view rhs) {
return lhs->Key() < rhs;
}
bool operator<(std::string_view lhs,
const scoped_refptr<MemoryCacheEntry>& rhs) {
return lhs < rhs->Key();
}
MemoryCache::MemoryCache(size_t max_size,
std::string_view cache_hit_trace_event)
: max_size_(max_size), cache_hit_trace_event_(cache_hit_trace_event) {}
MemoryCache::~MemoryCache() = default;
scoped_refptr<MemoryCacheEntry> MemoryCache::Find(std::string_view key) {
// Because we are tracking LRU, even loads modify internal state so mutex is
// required.
base::AutoLock lock(mutex_);
auto it = entries_.find(key);
if (it == entries_.end()) {
return nullptr;
}
if (!cache_hit_trace_event_.empty()) {
TRACE_EVENT0("gpu", cache_hit_trace_event_.c_str());
}
// Even if this was just a "peek" operation to get size, the entry was
// accessed so move it to the back of the eviction queue.
scoped_refptr<MemoryCacheEntry>& entry = *it;
entry->RemoveFromList();
lru_.Append(entry.get());
return entry;
}
scoped_refptr<MemoryCacheEntry> MemoryCache::Store(
std::string_view key,
base::span<const uint8_t> data) {
base::AutoLock lock(mutex_);
EvictEntry(key);
if (!CanFitMemoryCacheEntry(key.size() + data.size())) {
return nullptr;
}
auto entry = base::MakeRefCounted<MemoryCacheEntry>(key, data);
InsertEntry(entry);
return entry;
}
scoped_refptr<MemoryCacheEntry> MemoryCache::Store(
std::string_view key,
base::HeapArray<uint8_t> data) {
base::AutoLock lock(mutex_);
EvictEntry(key);
if (!CanFitMemoryCacheEntry(key.size() + data.size())) {
return nullptr;
}
auto entry = base::MakeRefCounted<MemoryCacheEntry>(key, std::move(data));
InsertEntry(entry);
return entry;
}
void MemoryCache::PurgeMemory(base::MemoryPressureLevel memory_pressure_level) {
base::AutoLock lock(mutex_);
size_t new_limit = gpu::UpdateShaderCacheSizeOnMemoryPressure(
max_size_, memory_pressure_level);
// Evict the least recently used entries until we reach the `new_limit`
while (current_size_ > new_limit) {
EvictEntry(lru_.head()->value());
}
}
void MemoryCache::OnMemoryDump(const std::string& dump_name,
base::trace_event::ProcessMemoryDump* pmd) {
base::AutoLock lock(mutex_);
using base::trace_event::MemoryAllocatorDump;
MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name);
dump->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, current_size_);
dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
MemoryAllocatorDump::kUnitsObjects, entries_.size());
}
void MemoryCache::EvictEntry(std::string_view key) {
if (auto it = entries_.find(key); it != entries_.end()) {
const scoped_refptr<MemoryCacheEntry>& entry = *it;
EvictEntry(entry.get());
}
}
void MemoryCache::EvictEntry(MemoryCacheEntry* entry) {
// Always remove the entry from the LRU first because removing it from the
// entry map will cause the entry to be destroyed.
entry->RemoveFromList();
// Update the size information.
current_size_ -= entry->TotalSize();
// Finally remove the entry from the map thereby destroying the entry.
entries_.erase(entry->Key());
}
void MemoryCache::InsertEntry(scoped_refptr<MemoryCacheEntry> entry) {
// Evict least used entries until we have enough room to add the new entry.
while (current_size_ + entry->TotalSize() > max_size_) {
EvictEntry(lru_.head()->value());
}
// Add the entry size to the overall size and update the eviction queue.
current_size_ += entry->TotalSize();
lru_.Append(entry.get());
auto [it, inserted] = entries_.insert(entry);
DCHECK(inserted);
}
bool MemoryCache::CanFitMemoryCacheEntry(size_t data_size) const {
// Don't need to do anything if we are not storing anything.
if (data_size == 0) {
return false;
}
// If this entry is larger than we can make space for, don't allow it to be
// stored.
if (data_size >= max_size_) {
return false;
}
return true;
}
} // namespace gpu