Skip to content

Commit a4f191a

Browse files
committed
Cobalt Image Cache: Implement pre-allocated pool, logging, and statistics
- Implemented ImageDecodeMemoryPool to manage a contiguous 32MB arena for image decodes, mitigating physical memory fragmentation. - Implemented PooledDiscardableMemory to allocate backing memory from the pool. - Integrated the pool into GpuImageDecodeCache::DecodeImageIfNecessary, controlled via the 'PreallocatedImageCachePool' base::Feature. - Added robust fallback to the default allocator if the pool runs out of memory. - Added detailed logs for SW/HW decodes and cache stats in 'MESSAGE: key=value' format. - Added explicit statistics tracking to ImageDecodeMemoryPool (allocations, failures) and integrated them into the periodic STATS log. TAG=agy CONV=d68ce74a-a9b1-496e-b926-4a5d9c9fdc14
1 parent 6033b93 commit a4f191a

4 files changed

Lines changed: 320 additions & 9 deletions

File tree

cc/base/features.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,10 @@ BASE_FEATURE_PARAM(base::TimeDelta,
281281
"max_animation_duration",
282282
base::Milliseconds(700));
283283

284+
#if BUILDFLAG(IS_COBALT)
285+
BASE_FEATURE(kPreallocatedImageCachePool,
286+
"PreallocatedImageCachePool",
287+
base::FEATURE_DISABLED_BY_DEFAULT);
288+
#endif
289+
284290
} // namespace features

cc/base/features.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ CC_BASE_EXPORT BASE_DECLARE_FEATURE_PARAM(double, kCubicBezierY2);
254254
CC_BASE_EXPORT BASE_DECLARE_FEATURE_PARAM(base::TimeDelta,
255255
kMaxAnimtionDuration);
256256

257+
#if BUILDFLAG(IS_COBALT)
258+
CC_BASE_EXPORT BASE_DECLARE_FEATURE(kPreallocatedImageCachePool);
259+
#endif
260+
257261
} // namespace features
258262

259263
#endif // CC_BASE_FEATURES_H_

cc/tiles/gpu_image_decode_cache.cc

Lines changed: 235 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
#include "base/numerics/safe_math.h"
2828
#include "base/strings/stringprintf.h"
2929
#if BUILDFLAG(IS_COBALT)
30+
#include <list>
31+
32+
#include "base/bits.h"
33+
#include "base/no_destructor.h"
3034
#include "base/strings/string_number_conversions.h"
3135
#endif
3236
#include "base/synchronization/lock.h"
@@ -456,6 +460,181 @@ sk_sp<SkImage> MakeTextureImage(viz::RasterContextProvider* context,
456460
return uploaded_image;
457461
}
458462

463+
#if BUILDFLAG(IS_COBALT)
464+
// A simple arena allocator for image decodes.
465+
class ImageDecodeMemoryPool {
466+
public:
467+
static ImageDecodeMemoryPool* GetInstance() {
468+
static base::NoDestructor<ImageDecodeMemoryPool> instance;
469+
return instance.get();
470+
}
471+
472+
ImageDecodeMemoryPool() {
473+
// Size the pool to match the decoded image working set budget.
474+
pool_size_ = ImageDecodeCacheUtils::GetWorkingSetBytesForImageDecode(
475+
/*for_renderer=*/false);
476+
arena_ = std::make_unique<char[]>(pool_size_);
477+
blocks_.push_back({0, pool_size_, true});
478+
LOG(INFO) << "COBALT_IMAGE_CACHE: POOL_CREATED: size=" << pool_size_;
479+
}
480+
481+
~ImageDecodeMemoryPool() = default;
482+
483+
void* Allocate(size_t size, size_t* allocated_size) {
484+
base::AutoLock lock(mutex_);
485+
// Align size to 8 bytes using Chromium helper.
486+
size = base::bits::AlignUp<size_t>(size, 8);
487+
488+
for (auto it = blocks_.begin(); it != blocks_.end(); ++it) {
489+
if (it->free && it->size >= size) {
490+
// Split block if it is larger than needed.
491+
if (it->size > size + 1024) {
492+
Block leftover = {it->offset + size, it->size - size, true};
493+
it->size = size;
494+
it->free = false;
495+
blocks_.insert(std::next(it), leftover);
496+
} else {
497+
it->free = false;
498+
}
499+
*allocated_size = it->size;
500+
void* ptr = arena_.get() + it->offset;
501+
VLOG(2) << "COBALT_IMAGE_CACHE: ALLOC: size=" << size
502+
<< ", actual=" << *allocated_size << ", offset=" << it->offset;
503+
total_allocs_++;
504+
return ptr;
505+
}
506+
}
507+
LOG(ERROR) << "COBALT_IMAGE_CACHE: POOL_OOM: size=" << size;
508+
total_fails_++;
509+
return nullptr;
510+
}
511+
512+
void Free(void* ptr) {
513+
base::AutoLock lock(mutex_);
514+
if (!ptr) {
515+
return;
516+
}
517+
518+
size_t offset = static_cast<char*>(ptr) - arena_.get();
519+
for (auto it = blocks_.begin(); it != blocks_.end(); ++it) {
520+
if (it->offset == offset) {
521+
DCHECK(!it->free);
522+
it->free = true;
523+
VLOG(2) << "COBALT_IMAGE_CACHE: FREE: offset=" << offset
524+
<< ", size=" << it->size;
525+
Coalesce();
526+
return;
527+
}
528+
}
529+
NOTREACHED()
530+
<< "COBALT_IMAGE_CACHE: Attempted to free pointer not in pool: " << ptr;
531+
}
532+
533+
size_t GetPoolSize() const { return pool_size_; }
534+
535+
struct PoolStats {
536+
size_t total_allocs;
537+
size_t total_fails;
538+
size_t free_size;
539+
size_t pool_size;
540+
size_t min_free_block_size;
541+
size_t max_free_block_size;
542+
};
543+
PoolStats GetStats() {
544+
base::AutoLock lock(mutex_);
545+
size_t free_size = 0;
546+
size_t min_free = pool_size_;
547+
size_t max_free = 0;
548+
bool has_free = false;
549+
for (const auto& block : blocks_) {
550+
if (block.free) {
551+
has_free = true;
552+
free_size += block.size;
553+
if (block.size < min_free) {
554+
min_free = block.size;
555+
}
556+
if (block.size > max_free) {
557+
max_free = block.size;
558+
}
559+
}
560+
}
561+
if (!has_free) {
562+
min_free = 0;
563+
max_free = 0;
564+
}
565+
return {total_allocs_, total_fails_, free_size,
566+
pool_size_, min_free, max_free};
567+
}
568+
569+
private:
570+
struct Block {
571+
size_t offset;
572+
size_t size;
573+
bool free;
574+
};
575+
576+
void Coalesce() {
577+
auto it = blocks_.begin();
578+
while (it != blocks_.end() && std::next(it) != blocks_.end()) {
579+
auto next = std::next(it);
580+
if (it->free && next->free) {
581+
it->size += next->size;
582+
blocks_.erase(next);
583+
} else {
584+
++it;
585+
}
586+
}
587+
}
588+
589+
size_t pool_size_;
590+
std::unique_ptr<char[]> arena_;
591+
std::list<Block> blocks_;
592+
base::Lock mutex_;
593+
size_t total_allocs_ = 0;
594+
size_t total_fails_ = 0;
595+
};
596+
597+
class PooledDiscardableMemory : public base::DiscardableMemory {
598+
public:
599+
explicit PooledDiscardableMemory(size_t size) : size_(size) {
600+
data_ =
601+
ImageDecodeMemoryPool::GetInstance()->Allocate(size, &allocated_size_);
602+
}
603+
604+
~PooledDiscardableMemory() override {
605+
if (data_) {
606+
ImageDecodeMemoryPool::GetInstance()->Free(data_);
607+
}
608+
}
609+
610+
[[nodiscard]] bool Lock() override { return data_ != nullptr; }
611+
612+
void Unlock() override {
613+
// Keep locked until destroyed.
614+
}
615+
616+
void* data() const override { return data_; }
617+
618+
void DiscardForTesting() override {
619+
// Not implemented.
620+
}
621+
622+
base::trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
623+
const char* name,
624+
base::trace_event::ProcessMemoryDump* pmd) const override {
625+
auto* dump = pmd->CreateAllocatorDump(name);
626+
dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
627+
base::trace_event::MemoryAllocatorDump::kUnitsBytes, size_);
628+
return dump;
629+
}
630+
631+
private:
632+
void* data_ = nullptr;
633+
size_t size_;
634+
size_t allocated_size_ = 0;
635+
};
636+
#endif // BUILDFLAG(IS_COBALT)
637+
459638
// We use this below, instead of just a std::unique_ptr, so that we can run
460639
// a Finch experiment to check the impact of not using discardable memory on the
461640
// GPU decode path.
@@ -2379,6 +2558,26 @@ bool GpuImageDecodeCache::ExceedsCacheLimits() const {
23792558
#endif
23802559
}
23812560

2561+
#if BUILDFLAG(IS_COBALT)
2562+
static int call_count = 0;
2563+
if (++call_count % 100 == 0) {
2564+
auto pool_stats = ImageDecodeMemoryPool::GetInstance()->GetStats();
2565+
LOG(INFO) << "COBALT_IMAGE_CACHE: STATS: items=" << persistent_cache_.size()
2566+
<< ", limit=" << items_limit
2567+
<< ", pool_allocs=" << pool_stats.total_allocs
2568+
<< ", pool_fails=" << pool_stats.total_fails
2569+
<< ", pool_free=" << pool_stats.free_size
2570+
<< ", pool_size=" << pool_stats.pool_size
2571+
<< ", min_block_kb=" << (pool_stats.min_free_block_size / 1024)
2572+
<< ", max_block_kb=" << (pool_stats.max_free_block_size / 1024);
2573+
}
2574+
#else
2575+
static int call_count = 0;
2576+
if (++call_count % 100 == 0) {
2577+
LOG(INFO) << "COBALT_IMAGE_CACHE: STATS: items=" << persistent_cache_.size()
2578+
<< ", limit=" << items_limit;
2579+
}
2580+
#endif
23822581
return persistent_cache_.size() > items_limit;
23832582
}
23842583

@@ -2509,15 +2708,35 @@ void GpuImageDecodeCache::DecodeImageIfNecessary(
25092708

25102709
// Allocate the backing memory for the decode.
25112710
std::unique_ptr<base::DiscardableMemory> backing_memory;
2512-
if (base::FeatureList::IsEnabled(
2513-
features::kNoDiscardableMemoryForGpuDecodePath)) {
2514-
backing_memory = std::make_unique<HeapDiscardableMemory>(info.size);
2515-
} else {
2516-
auto* allocator = base::DiscardableMemoryAllocator::GetInstance();
2517-
backing_memory =
2518-
allocator->AllocateLockedDiscardableMemoryWithRetryOrDie(
2519-
info.size, base::BindOnce(&GpuImageDecodeCache::ClearCache,
2520-
base::Unretained(this)));
2711+
#if BUILDFLAG(IS_COBALT)
2712+
static const bool use_pooled_memory =
2713+
base::FeatureList::IsEnabled(features::kPreallocatedImageCachePool);
2714+
if (use_pooled_memory) {
2715+
auto pooled_memory =
2716+
std::make_unique<PooledDiscardableMemory>(info.size);
2717+
if (!pooled_memory->Lock()) {
2718+
ClearCache();
2719+
pooled_memory = std::make_unique<PooledDiscardableMemory>(info.size);
2720+
if (!pooled_memory->Lock()) {
2721+
LOG(WARNING) << "COBALT_IMAGE_CACHE: POOL_FALLBACK: size="
2722+
<< info.size;
2723+
pooled_memory.reset();
2724+
}
2725+
}
2726+
backing_memory = std::move(pooled_memory);
2727+
}
2728+
#endif
2729+
if (!backing_memory) {
2730+
if (base::FeatureList::IsEnabled(
2731+
features::kNoDiscardableMemoryForGpuDecodePath)) {
2732+
backing_memory = std::make_unique<HeapDiscardableMemory>(info.size);
2733+
} else {
2734+
auto* allocator = base::DiscardableMemoryAllocator::GetInstance();
2735+
backing_memory =
2736+
allocator->AllocateLockedDiscardableMemoryWithRetryOrDie(
2737+
info.size, base::BindOnce(&GpuImageDecodeCache::ClearCache,
2738+
base::Unretained(this)));
2739+
}
25212740
}
25222741

25232742
// Do the decode.
@@ -2591,6 +2810,10 @@ void GpuImageDecodeCache::DecodeImageIfNecessary(
25912810

25922811
image_data->decode.SetLockedData(aux_image_data,
25932812
task_type == TaskType::kOutOfRaster);
2813+
LOG(INFO) << "COBALT_IMAGE_CACHE: SW_DECODE: id="
2814+
<< image_data->paint_image_id
2815+
<< ", size=" << draw_image.paint_image().width() << "x"
2816+
<< draw_image.paint_image().height();
25942817
}
25952818

25962819
void GpuImageDecodeCache::GenerateDarkModeFilter(const DrawImage& draw_image,
@@ -2728,6 +2951,9 @@ void GpuImageDecodeCache::UploadImageIfNecessary_TransferCache_HardwareDecode(
27282951
draw_image.paint_image().GetSwSkImage()->refEncodedData();
27292952
DCHECK(encoded_data);
27302953
const uint32_t transfer_cache_id = ClientImageTransferCacheEntry::GetNextId();
2954+
LOG(INFO) << "COBALT_IMAGE_CACHE: HW_DECODE: id="
2955+
<< image_data->paint_image_id << ", size=" << output_size.ToString()
2956+
<< ", encoded_size=" << encoded_data->size();
27312957
const gpu::SyncToken decode_sync_token =
27322958
context_->RasterInterface()->ScheduleImageDecode(
27332959
gfx::SkDataToSpan(encoded_data), output_size, transfer_cache_id,

0 commit comments

Comments
 (0)