diff options
author | 2024-10-25 17:34:49 +0100 | |
---|---|---|
committer | 2024-10-28 18:19:29 +0000 | |
commit | 1f9c184392020cb5c4bdf453f4c8847ca389614b (patch) | |
tree | a2c535a1ee4353f880215297a2ddb78a983968ee | |
parent | 323dfbf34ff435f673e83c86af6e957d7d4a2369 (diff) |
Always use an array in the DexCache for ArtField and ArtMethod.
The lookup success rate is too low (<1%) during app startup where the
cache is most stressed. So always use an array for them.
To avoid memory regression, madvise away the arrays at every GC.
go/art-benchmark-service reports ~3% improvement in app startup with no
statistically significant memory regression.
Test: test.py
Change-Id: Id13612054943ed7770c9e96756f391eed2352d79
-rw-r--r-- | runtime/class_linker.cc | 16 | ||||
-rw-r--r-- | runtime/mirror/dex_cache.cc | 32 | ||||
-rw-r--r-- | runtime/mirror/dex_cache.h | 8 | ||||
-rw-r--r-- | runtime/oat/image.cc | 4 |
4 files changed, 55 insertions, 5 deletions
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 090ee42a08..5d76f802ee 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -10977,8 +10977,24 @@ void ClassLinker::InsertDexFileInToClassLoader(ObjPtr<mirror::Object> dex_file, } } +class ReclaimMemoryDexCacheVisitor : public DexCacheVisitor { + public: + ReclaimMemoryDexCacheVisitor() {} + + void Visit(ObjPtr<mirror::DexCache> dex_cache) + REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override { + dex_cache->ReclaimMemory(); + } +}; + void ClassLinker::CleanupClassLoaders() { Thread* const self = Thread::Current(); + // We clear dex cache arrays for every GC. + { + ReaderMutexLock mu(self, *Locks::dex_lock_); + ReclaimMemoryDexCacheVisitor visitor; + VisitDexCaches(&visitor); + } std::list<ClassLoaderData> to_delete; // Do the delete outside the lock to avoid lock violation in jit code cache. { diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc index b981f08d97..abbd23442b 100644 --- a/runtime/mirror/dex_cache.cc +++ b/runtime/mirror/dex_cache.cc @@ -224,7 +224,6 @@ void DexCache::SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved) SetResolvedTypesEntry(type_idx.index_, resolved.Ptr()); // TODO: Fine-grained marking, so that we don't need to go through all arrays in full. WriteBarrier::ForEveryFieldWrite(this); - if (this == resolved->GetDexCache()) { // If we're updating the dex cache of the class, optimistically update the cache for methods and // fields if the caches are full arrays. @@ -255,5 +254,36 @@ void DexCache::SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved) } } +static void ReclaimMemoryInternal(uint64_t address, size_t size) { + if (address == 0u || size <= MemMap::GetPageSize()) { + return; + } + uint8_t* const mem_begin = reinterpret_cast<uint8_t*>(address); + uint8_t* const mem_end = reinterpret_cast<uint8_t*>(address + size); + uint8_t* const page_begin = AlignUp(mem_begin, MemMap::GetPageSize()); + uint8_t* const page_end = AlignDown(mem_end, MemMap::GetPageSize()); + DCHECK_GE(page_end, page_begin); + if (page_end == page_begin) { + return; + } + bool res = madvise(page_begin, page_end - page_begin, MADV_DONTNEED); + if (res == -1) { + PLOG(WARNING) << "madvise failed"; + } +} + +void DexCache::ReclaimMemory() { + ReclaimMemoryInternal(resolved_fields_array_, + NumResolvedFieldsArray() * sizeof(ArtField*)); + ReclaimMemoryInternal(resolved_method_types_array_, + NumResolvedMethodTypesArray() * sizeof(GcRoot<mirror::MethodType>)); + ReclaimMemoryInternal(resolved_methods_array_, + NumResolvedMethodsArray() * sizeof(ArtMethod*)); + ReclaimMemoryInternal(resolved_types_array_, + NumResolvedTypesArray() * sizeof(GcRoot<mirror::Class>)); + ReclaimMemoryInternal(strings_array_, + NumStringsArray() * sizeof(GcRoot<mirror::String>)); +} + } // namespace mirror } // namespace art diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h index c1d8cd8335..69e610d9dc 100644 --- a/runtime/mirror/dex_cache.h +++ b/runtime/mirror/dex_cache.h @@ -274,12 +274,14 @@ class MANAGED DexCache final : public Object { "String dex cache size is not a power of 2."); // Size of field dex cache. Needs to be a power of 2 for entrypoint assumptions to hold. - static constexpr size_t kDexCacheFieldCacheSize = 1024; + // Set to the maximum number of field IDs to always use a direct array. + static constexpr size_t kDexCacheFieldCacheSize = 65536; static_assert(IsPowerOfTwo(kDexCacheFieldCacheSize), "Field dex cache size is not a power of 2."); // Size of method dex cache. Needs to be a power of 2 for entrypoint assumptions to hold. - static constexpr size_t kDexCacheMethodCacheSize = 1024; + // Set to the maximum number of method IDs to always use a direct array. + static constexpr size_t kDexCacheMethodCacheSize = 65536; static_assert(IsPowerOfTwo(kDexCacheMethodCacheSize), "Method dex cache size is not a power of 2."); @@ -392,6 +394,8 @@ class MANAGED DexCache final : public Object { return number_of_elements <= dex_cache_size; } + // Returns to the OS memory used for dex cache arrays. + void ReclaimMemory() REQUIRES_SHARED(Locks::mutator_lock_); // NOLINTBEGIN(bugprone-macro-parentheses) #define DEFINE_ARRAY(name, array_kind, getter_setter, type, ids, alloc_kind) \ diff --git a/runtime/oat/image.cc b/runtime/oat/image.cc index 7dd76c3757..1c8517bc77 100644 --- a/runtime/oat/image.cc +++ b/runtime/oat/image.cc @@ -34,8 +34,8 @@ namespace art HIDDEN { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -// Last change: Add intrinsics for Unsafe/JdkUnsafe.{get,put}Int(long[, int]). -const uint8_t ImageHeader::kImageVersion[] = { '1', '1', '5', '\0' }; +// Use arrays for resolved methods and resolved fields in DexCache. +const uint8_t ImageHeader::kImageVersion[] = { '1', '1', '6', '\0' }; ImageHeader::ImageHeader(uint32_t image_reservation_size, uint32_t component_count, |