Reduce cache memory use of libdexfile.
It is excessive to cache all methods of all open dex files.
Cache only methods that have been looked up by dex offset.
Create class lookup table to keep method lookup efficient
(linear search, but only within the methods of the class).
This reduces memory use by over 10x.
Bug: 152976928
Test: ./art/test.py -r -b --host -t 137
Test: libunwindstack_unit_test
Change-Id: I0aee3d9a1060bf254b156836c428a17e6f1ee70c
diff --git a/libdexfile/external/dex_file_ext.cc b/libdexfile/external/dex_file_ext.cc
index e1b7874..c574ff3 100644
--- a/libdexfile/external/dex_file_ext.cc
+++ b/libdexfile/external/dex_file_ext.cc
@@ -25,6 +25,7 @@
#include <cerrno>
#include <cstring>
+#include <deque>
#include <map>
#include <memory>
#include <string>
@@ -99,15 +100,6 @@
// Wraps DexFile to add the caching needed by the external interface. This is
// what gets passed over as ExtDexFile*.
struct ExtDexFile {
- private:
- // Method cache for GetMethodInfoForOffset. This is populated as we iterate
- // sequentially through the class defs. MethodCacheEntry.name is only set for
- // methods returned by GetMethodInfoForOffset.
- std::map<int32_t, art::MethodCacheEntry> method_cache_;
-
- // Index of first class def for which method_cache_ isn't complete.
- uint32_t class_def_index_ = 0;
-
public:
std::unique_ptr<const art::DexFile> dex_file_;
explicit ExtDexFile(std::unique_ptr<const art::DexFile>&& dex_file)
@@ -120,8 +112,9 @@
return &it->second;
}
- for (; class_def_index_ < dex_file_->NumClassDefs(); class_def_index_++) {
- art::ClassAccessor accessor(*dex_file_, class_def_index_);
+ uint32_t class_def_index;
+ if (GetClassDefIndex(dex_offset, &class_def_index)) {
+ art::ClassAccessor accessor(*dex_file_, class_def_index);
for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
art::CodeItemInstructionAccessor code = method.GetInstructions();
@@ -131,9 +124,9 @@
int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
int32_t len = code.InsnsSizeInBytes();
- int32_t index = method.GetIndex();
- auto res = method_cache_.emplace(offset + len, art::MethodCacheEntry{offset, len, index});
if (offset <= dex_offset && dex_offset < offset + len) {
+ int32_t index = method.GetIndex();
+ auto res = method_cache_.emplace(offset + len, art::MethodCacheEntry{offset, len, index});
return &res.first->second;
}
}
@@ -141,6 +134,56 @@
return nullptr;
}
+
+ private:
+ bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) {
+ if (class_cache_.empty()) {
+ // Create binary search table with (end_dex_offset, class_def_index) entries.
+ // That is, we don't assume that dex code of given class is consecutive.
+ std::deque<std::pair<uint32_t, uint32_t>> cache;
+ for (art::ClassAccessor accessor : dex_file_->GetClasses()) {
+ for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
+ art::CodeItemInstructionAccessor code = method.GetInstructions();
+ if (code.HasCodeItem()) {
+ int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
+ DCHECK_NE(offset, 0);
+ cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex());
+ }
+ }
+ }
+ std::sort(cache.begin(), cache.end());
+
+ // If two consecutive methods belong to same class, we can merge them.
+ // This tends to reduce the number of entries (used memory) by 10x.
+ size_t num_entries = cache.size();
+ if (cache.size() > 1) {
+ for (auto it = std::next(cache.begin()); it != cache.end(); it++) {
+ if (std::prev(it)->second == it->second) {
+ std::prev(it)->first = 0; // Clear entry with lower end_dex_offset (mark to remove).
+ num_entries--;
+ }
+ }
+ }
+
+ // The cache is immutable now. Store it as continuous vector to save space.
+ class_cache_.reserve(num_entries);
+ auto pred = [](auto it) { return it.first != 0; }; // Entries to copy (not cleared above).
+ std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred);
+ }
+
+ // Binary search in the class cache. First element of the pair is the key.
+ auto comp = [](uint32_t value, const auto& it) { return value < it.first; };
+ auto it = std::upper_bound(class_cache_.begin(), class_cache_.end(), dex_offset, comp);
+ if (it != class_cache_.end()) {
+ *class_def_index = it->second;
+ return true;
+ }
+ return false;
+ }
+
+ // Binary search table with (end_dex_offset, class_def_index) entries.
+ std::vector<std::pair<uint32_t, uint32_t>> class_cache_;
+ std::map<uint32_t, art::MethodCacheEntry> method_cache_; // end_dex_offset -> method.
};
int ExtDexFileOpenFromMemory(const void* addr,