summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nicolas Geoffray <ngeoffray@google.com> 2024-10-25 17:34:49 +0100
committer Nicolas Geoffray <ngeoffray@google.com> 2024-10-28 18:19:29 +0000
commit1f9c184392020cb5c4bdf453f4c8847ca389614b (patch)
treea2c535a1ee4353f880215297a2ddb78a983968ee
parent323dfbf34ff435f673e83c86af6e957d7d4a2369 (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.cc16
-rw-r--r--runtime/mirror/dex_cache.cc32
-rw-r--r--runtime/mirror/dex_cache.h8
-rw-r--r--runtime/oat/image.cc4
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,