Optimize boot-image dirty object layout
Add dirty object offsets to imgdiag output
Modify dex2oat dirty-image-objects handling to match imgdiag output by
offsets and bin dirty objects
Test: ART APEX install with old/new dirty-image-objects format
Test: measure dirty page count with imgdiag
Change-Id: I386711c8b31e0770518f18faa699cfe1aa7549a5
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 2c3c220..577143c 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -21,10 +21,12 @@
#include <sys/stat.h>
#include <zlib.h>
+#include <charconv>
#include <memory>
#include <numeric>
#include <vector>
+#include "android-base/strings.h"
#include "art_field-inl.h"
#include "art_method-inl.h"
#include "base/callee_save_type.h"
@@ -334,6 +336,13 @@
TimingLogger::ScopedTiming t("CalculateNewObjectOffsets", timings);
ScopedObjectAccess soa(self);
CalculateNewObjectOffsets();
+
+ // If dirty_image_objects_ is present - try optimizing object layout.
+ // It can only be done after the first CalculateNewObjectOffsets,
+ // because calculated offsets are used to match dirty objects between imgdiag and dex2oat.
+ if (compiler_options_.IsBootImage() && dirty_image_objects_ != nullptr) {
+ TryRecalculateOffsetsWithDirtyObjects();
+ }
}
// This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and
@@ -729,7 +738,13 @@
// so packing them together will not result in a noticeably tighter dirty-to-clean ratio.
//
ObjPtr<mirror::Class> klass = object->GetClass<kVerifyNone, kWithoutReadBarrier>();
- if (klass->IsClassClass()) {
+ if (klass->IsStringClass<kVerifyNone>()) {
+ // Assign strings to their bin before checking dirty objects, because
+ // string intern processing expects strings to be in Bin::kString.
+ bin = Bin::kString; // Strings are almost always immutable (except for object header).
+ } else if (dirty_objects_.find(object) != dirty_objects_.end()) {
+ bin = Bin::kKnownDirty;
+ } else if (klass->IsClassClass()) {
bin = Bin::kClassVerified;
ObjPtr<mirror::Class> as_klass = object->AsClass<kVerifyNone>();
@@ -766,8 +781,6 @@
}
}
}
- } else if (klass->IsStringClass<kVerifyNone>()) {
- bin = Bin::kString; // Strings are almost always immutable (except for object header).
} else if (!klass->HasSuperClass()) {
// Only `j.l.Object` and primitive classes lack the superclass and
// there are no instances of primitive classes.
@@ -2533,6 +2546,142 @@
}
}
+std::optional<HashSet<mirror::Object*>> ImageWriter::MatchDirtyObjectOffsets(
+ const HashMap<uint32_t, DirtyEntry>& dirty_entries) REQUIRES_SHARED(Locks::mutator_lock_) {
+ HashSet<mirror::Object*> dirty_objects;
+ bool mismatch_found = false;
+
+ auto visitor = [&](Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(obj != nullptr);
+ if (mismatch_found) {
+ return;
+ }
+ if (!IsImageBinSlotAssigned(obj)) {
+ return;
+ }
+
+ uint8_t* image_address = reinterpret_cast<uint8_t*>(GetImageAddress(obj));
+ uint32_t offset = static_cast<uint32_t>(image_address - global_image_begin_);
+
+ auto entry_it = dirty_entries.find(offset);
+ if (entry_it == dirty_entries.end()) {
+ return;
+ }
+
+ const DirtyEntry& entry = entry_it->second;
+
+ const bool is_class = obj->IsClass();
+ const uint32_t descriptor_hash =
+ is_class ? obj->AsClass()->DescriptorHash() : obj->GetClass()->DescriptorHash();
+
+ if (is_class != entry.is_class || descriptor_hash != entry.descriptor_hash) {
+ LOG(WARNING) << "Dirty image objects offset mismatch (outdated file?)";
+ mismatch_found = true;
+ return;
+ }
+
+ dirty_objects.insert(obj);
+ };
+ Runtime::Current()->GetHeap()->VisitObjects(visitor);
+
+ // A single mismatch indicates that dirty-image-objects layout differs from
+ // current ImageWriter layout. In this case any "valid" matches are likely to be accidental,
+ // so there's no point in optimizing the layout with such data.
+ if (mismatch_found) {
+ return {};
+ }
+ if (dirty_objects.size() != dirty_entries.size()) {
+ LOG(WARNING) << "Dirty image objects missing offsets (outdated file?)";
+ return {};
+ }
+ return dirty_objects;
+}
+
+void ImageWriter::ResetObjectOffsets() {
+ const size_t image_infos_size = image_infos_.size();
+ image_infos_.clear();
+ image_infos_.resize(image_infos_size);
+
+ native_object_relocations_.clear();
+
+ // CalculateNewObjectOffsets stores image offsets of the objects in lock words,
+ // while original lock words are preserved in saved_hashcode_map.
+ // Restore original lock words.
+ auto visitor = [&](Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(obj != nullptr);
+ const auto it = saved_hashcode_map_.find(obj);
+ obj->SetLockWord(it != saved_hashcode_map_.end() ? LockWord::FromHashCode(it->second, 0u) :
+ LockWord::Default(),
+ false);
+ };
+ Runtime::Current()->GetHeap()->VisitObjects(visitor);
+
+ saved_hashcode_map_.clear();
+}
+
+void ImageWriter::TryRecalculateOffsetsWithDirtyObjects() {
+ const std::optional<HashMap<uint32_t, ImageWriter::DirtyEntry>> dirty_entries =
+ ParseDirtyObjectOffsets(*dirty_image_objects_);
+ if (!dirty_entries || dirty_entries->empty()) {
+ return;
+ }
+
+ std::optional<HashSet<mirror::Object*>> dirty_objects = MatchDirtyObjectOffsets(*dirty_entries);
+ if (!dirty_objects || dirty_objects->empty()) {
+ return;
+ }
+ // Calculate offsets again, now with dirty object offsets.
+ LOG(INFO) << "Recalculating object offsets using dirty-image-objects";
+ dirty_objects_ = std::move(*dirty_objects);
+ ResetObjectOffsets();
+ CalculateNewObjectOffsets();
+}
+
+std::optional<HashMap<uint32_t, ImageWriter::DirtyEntry>> ImageWriter::ParseDirtyObjectOffsets(
+ const HashSet<std::string>& dirty_image_objects) REQUIRES_SHARED(Locks::mutator_lock_) {
+ HashMap<uint32_t, DirtyEntry> dirty_entries;
+
+ // Go through each dirty-image-object line, parse only lines of the format:
+ // "dirty_obj: <offset> <type> <descriptor_hash>"
+ // <offset> -- decimal uint32.
+ // <type> -- "class" or "instance" (defines if descriptor is referring to a class or an instance).
+ // <descriptor_hash> -- decimal uint32 (from DescriptorHash() method).
+ const std::string prefix = "dirty_obj:";
+ for (const std::string& entry_str : dirty_image_objects) {
+ // Skip the lines of old dirty-image-object format.
+ if (std::strncmp(entry_str.data(), prefix.data(), prefix.size()) != 0) {
+ continue;
+ }
+
+ const std::vector<std::string> tokens = android::base::Split(entry_str, " ");
+ if (tokens.size() != 4) {
+ LOG(WARNING) << "Invalid dirty image objects format: \"" << entry_str << "\"";
+ return {};
+ }
+
+ uint32_t offset = 0;
+ std::from_chars_result res =
+ std::from_chars(tokens[1].data(), tokens[1].data() + tokens[1].size(), offset);
+ if (res.ec != std::errc()) {
+ LOG(WARNING) << "Couldn't parse dirty object offset: \"" << entry_str << "\"";
+ return {};
+ }
+
+ DirtyEntry entry;
+ entry.is_class = (tokens[2] == "class");
+ res = std::from_chars(
+ tokens[3].data(), tokens[3].data() + tokens[3].size(), entry.descriptor_hash);
+ if (res.ec != std::errc()) {
+ LOG(WARNING) << "Couldn't parse dirty object descriptor hash: \"" << entry_str << "\"";
+ return {};
+ }
+
+ dirty_entries.insert(std::make_pair(offset, entry));
+ }
+
+ return dirty_entries;
+}
+
std::pair<size_t, dchecked_vector<ImageSection>>
ImageWriter::ImageInfo::CreateImageSections() const {
dchecked_vector<ImageSection> sections(ImageHeader::kSectionCount);
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index e5eeacc..b6535c3 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -462,6 +462,23 @@
REQUIRES_SHARED(Locks::mutator_lock_);
void CalculateObjectBinSlots(mirror::Object* obj)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Undo the changes of CalculateNewObjectOffsets.
+ void ResetObjectOffsets() REQUIRES_SHARED(Locks::mutator_lock_);
+ // Reset and calculate new offsets with dirty objects optimization.
+ // Does nothing if dirty object offsets don't match with current offsets.
+ void TryRecalculateOffsetsWithDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Dirty object data from dirty-image-objects.
+ struct DirtyEntry {
+ uint32_t descriptor_hash = 0;
+ bool is_class = false;
+ };
+ // Parse dirty-image-objects into (offset->entry) map. Returns nullopt on parse error.
+ static std::optional<HashMap<uint32_t, DirtyEntry>> ParseDirtyObjectOffsets(
+ const HashSet<std::string>& dirty_image_objects) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Get all objects that match dirty_entries by offset. Returns nullopt if there is a mismatch.
+ std::optional<HashSet<mirror::Object*>> MatchDirtyObjectOffsets(
+ const HashMap<uint32_t, DirtyEntry>& dirty_entries) REQUIRES_SHARED(Locks::mutator_lock_);
// Creates the contiguous image in memory and adjusts pointers.
void CopyAndFixupNativeData(size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -685,9 +702,14 @@
// Map of dex files to the indexes of oat files that they were compiled into.
const HashMap<const DexFile*, size_t>& dex_file_oat_index_map_;
- // Set of objects known to be dirty in the image. Can be nullptr if there are none.
+ // Set of classes/objects known to be dirty in the image. Can be nullptr if there are none.
+ // For old dirty-image-objects format this set contains descriptors of dirty classes.
+ // For new format -- a set of dirty object offsets and descriptor hashes.
const HashSet<std::string>* dirty_image_objects_;
+ // Dirty object instances parsed from dirty_image_object_
+ HashSet<mirror::Object*> dirty_objects_;
+
// Objects are guaranteed to not cross the region size boundary.
size_t region_size_ = 0u;
diff --git a/imgdiag/Android.bp b/imgdiag/Android.bp
index afd86a0..a0354ab 100644
--- a/imgdiag/Android.bp
+++ b/imgdiag/Android.bp
@@ -84,6 +84,7 @@
"libartd",
"libartbased",
"libartd-compiler",
+ "libdexfiled",
],
apex_available: [
"com.android.art.debug",
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index fe70b10..ed2ff72 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -450,17 +450,22 @@
void DiffEntryContents(mirror::Object* entry,
uint8_t* remote_bytes,
const uint8_t* base_ptr,
- bool log_dirty_objects)
- REQUIRES_SHARED(Locks::mutator_lock_) {
+ bool log_dirty_objects,
+ size_t entry_offset) REQUIRES_SHARED(Locks::mutator_lock_) {
const char* tabs = " ";
// Attempt to find fields for all dirty bytes.
mirror::Class* klass = entry->GetClass();
+ std::string temp;
if (entry->IsClass()) {
os_ << tabs
<< "Class " << mirror::Class::PrettyClass(entry->AsClass()) << " " << entry << "\n";
+ os_ << tabs << "dirty_obj: " << entry_offset << " class "
+ << entry->AsClass()->DescriptorHash() << "\n";
} else {
os_ << tabs
<< "Instance of " << mirror::Class::PrettyClass(klass) << " " << entry << "\n";
+ os_ << tabs << "dirty_obj: " << entry_offset << " instance " << klass->DescriptorHash()
+ << "\n";
}
PrintEntryPages(reinterpret_cast<uintptr_t>(entry), EntrySize(entry), os_);
@@ -776,7 +781,8 @@
void DiffEntryContents(ArtMethod* method,
uint8_t* remote_bytes,
const uint8_t* base_ptr,
- bool log_dirty_objects ATTRIBUTE_UNUSED)
+ bool log_dirty_objects ATTRIBUTE_UNUSED,
+ size_t entry_offset ATTRIBUTE_UNUSED)
REQUIRES_SHARED(Locks::mutator_lock_) {
const char* tabs = " ";
os_ << tabs << "ArtMethod " << ArtMethod::PrettyMethod(method) << "\n";
@@ -1047,10 +1053,8 @@
uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry);
ptrdiff_t offset = entry_bytes - begin_image_ptr;
uint8_t* remote_bytes = &contents[offset];
- RegionSpecializedBase<T>::DiffEntryContents(entry,
- remote_bytes,
- &base_ptr[offset],
- log_dirty_objects);
+ RegionSpecializedBase<T>::DiffEntryContents(
+ entry, remote_bytes, &base_ptr[offset], log_dirty_objects, static_cast<size_t>(offset));
}
}