Reduced memory usage of primitive fields smaller than 4-bytes
Reduced memory used by byte and boolean fields from 4 bytes down to a
single byte and shorts and chars down to two bytes. Fields are now
arranged as Reference followed by decreasing component sizes, with
fields shuffled forward as needed.
Bug: 8135266
Change-Id: I65eaf31ed27e5bd5ba0c7d4606454b720b074752
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index dcf8f5f..9d85fa6 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2078,6 +2078,8 @@
const DexFile::ClassDef& dex_class_def) {
const byte* class_data = dex_file.GetClassData(dex_class_def);
size_t num_ref = 0;
+ size_t num_8 = 0;
+ size_t num_16 = 0;
size_t num_32 = 0;
size_t num_64 = 0;
if (class_data != NULL) {
@@ -2085,16 +2087,33 @@
const DexFile::FieldId& field_id = dex_file.GetFieldId(it.GetMemberIndex());
const char* descriptor = dex_file.GetFieldTypeDescriptor(field_id);
char c = descriptor[0];
- if (c == 'L' || c == '[') {
- num_ref++;
- } else if (c == 'J' || c == 'D') {
- num_64++;
- } else {
- num_32++;
+ switch (c) {
+ case 'L':
+ case '[':
+ num_ref++;
+ break;
+ case 'J':
+ case 'D':
+ num_64++;
+ break;
+ case 'I':
+ case 'F':
+ num_32++;
+ break;
+ case 'S':
+ case 'C':
+ num_16++;
+ break;
+ case 'B':
+ case 'Z':
+ num_8++;
+ break;
+ default:
+ LOG(FATAL) << "Unknown descriptor: " << c;
}
}
}
- return mirror::Class::ComputeClassSize(false, 0, num_32, num_64, num_ref);
+ return mirror::Class::ComputeClassSize(false, 0, num_8, num_16, num_32, num_64, num_ref);
}
OatFile::OatClass ClassLinker::FindOatClass(const DexFile& dex_file, uint16_t class_def_idx,
@@ -2455,6 +2474,89 @@
have_portable_code);
}
+template<int n>
+void ClassLinker::AlignFields(size_t& current_field, const size_t num_fields,
+ MemberOffset& field_offset,
+ mirror::ObjectArray<mirror::ArtField>* fields,
+ std::deque<mirror::ArtField*>& grouped_and_sorted_fields) {
+ if (current_field != num_fields && !IsAligned<n>(field_offset.Uint32Value())) {
+ size_t gap = (n - (field_offset.Uint32Value() & (n - 1)));
+ // Avoid padding unless a field that requires alignment actually exists.
+ bool needs_padding = false;
+ for (size_t i = 0; i < grouped_and_sorted_fields.size(); ++i) {
+ mirror::ArtField* field = grouped_and_sorted_fields[i];
+ Primitive::Type type = field->GetTypeAsPrimitiveType();
+ CHECK(type != Primitive::kPrimNot) << PrettyField(field); // should be primitive types
+ // Too big to fill the gap.
+ if (Primitive::ComponentSize(type) >= n) {
+ needs_padding = true;
+ continue;
+ }
+ if (needs_padding) {
+ // Shift as many fields as possible to fill the gaps.
+ size_t cursor = i;
+ mirror::ArtField* shift_field;
+ Primitive::Type shift_type;
+ while (cursor < grouped_and_sorted_fields.size() && gap > 0) {
+ // Find field that can current in current gap.
+ do {
+ DCHECK_LT(cursor, grouped_and_sorted_fields.size()) << "Cursor overran fields.";
+ shift_field = grouped_and_sorted_fields[cursor];
+ shift_type = shift_field->GetTypeAsPrimitiveType();
+ CHECK(shift_type != Primitive::kPrimNot) << PrettyField(shift_field);
+ // Can fit.
+ if (Primitive::ComponentSize(shift_type) <= gap) {
+ break;
+ }
+ ++cursor;
+ } while (cursor < grouped_and_sorted_fields.size());
+
+ if (cursor < grouped_and_sorted_fields.size()) {
+ fields->Set<false>(current_field++, shift_field);
+ shift_field->SetOffset(field_offset);
+ field_offset = MemberOffset(field_offset.Uint32Value() +
+ Primitive::ComponentSize(shift_type));
+ gap -= Primitive::ComponentSize(shift_type);
+ grouped_and_sorted_fields.erase(grouped_and_sorted_fields.begin() + cursor);
+ }
+ }
+ }
+ break;
+ }
+ // No further shuffling available, pad whatever space is left.
+ if (needs_padding) {
+ field_offset = MemberOffset(field_offset.Uint32Value() + gap);
+ }
+ DCHECK(!needs_padding || IsAligned<n>(field_offset.Uint32Value())) << "Needed " <<
+ n << " byte alignment, but not aligned after align with offset: " <<
+ field_offset.Uint32Value();
+ }
+}
+
+template<int n>
+void ClassLinker::ShuffleForward(size_t ¤t_field, const size_t num_fields,
+ MemberOffset& field_offset,
+ mirror::ObjectArray<mirror::ArtField>* fields,
+ std::deque<mirror::ArtField*>& grouped_and_sorted_fields) {
+ while (!grouped_and_sorted_fields.empty() && current_field != num_fields) {
+ mirror::ArtField* field = grouped_and_sorted_fields.front();
+ Primitive::Type type = field->GetTypeAsPrimitiveType();
+ CHECK(type != Primitive::kPrimNot) << PrettyField(field); // should be primitive types
+ if (Primitive::ComponentSize(type) != n) {
+ DCHECK_LT(Primitive::ComponentSize(type), static_cast<unsigned int>(n)) <<
+ "Encountered a larger field (" << Primitive::ComponentSize(type) << ") " <<
+ "while shuffling fields of size: " << n;
+ // We should've shuffled all field of size n forward by this point.
+ break;
+ }
+ DCHECK(IsAligned<n>(field_offset.Uint32Value()));
+ grouped_and_sorted_fields.pop_front();
+ fields->Set<false>(current_field++, field);
+ field->SetOffset(field_offset);
+ field_offset = MemberOffset(field_offset.Uint32Value() + n);
+ }
+}
+
void ClassLinker::LoadClass(const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def,
ConstHandle<mirror::Class> klass,
@@ -4674,20 +4776,20 @@
// No thread safety analysis as will be called from STL. Checked lock held in constructor.
bool operator()(mirror::ArtField* field1, mirror::ArtField* field2)
NO_THREAD_SAFETY_ANALYSIS {
- // First come reference fields, then 64-bit, and finally 32-bit
+ // First come reference fields, then 64-bit, then 32-bit, and then 16-bit, then finally 8-bit.
Primitive::Type type1 = field1->GetTypeAsPrimitiveType();
Primitive::Type type2 = field2->GetTypeAsPrimitiveType();
if (type1 != type2) {
bool is_primitive1 = type1 != Primitive::kPrimNot;
bool is_primitive2 = type2 != Primitive::kPrimNot;
- bool is64bit1 = is_primitive1 && (type1 == Primitive::kPrimLong ||
- type1 == Primitive::kPrimDouble);
- bool is64bit2 = is_primitive2 && (type2 == Primitive::kPrimLong ||
- type2 == Primitive::kPrimDouble);
- int order1 = !is_primitive1 ? 0 : (is64bit1 ? 1 : 2);
- int order2 = !is_primitive2 ? 0 : (is64bit2 ? 1 : 2);
- if (order1 != order2) {
- return order1 < order2;
+ if (type1 != type2) {
+ if (is_primitive1 && is_primitive2) {
+ // Larger primitive types go first.
+ return Primitive::ComponentSize(type1) > Primitive::ComponentSize(type2);
+ } else {
+ // Reference always goes first.
+ return !is_primitive1;
+ }
}
}
// same basic group? then sort by string.
@@ -4709,7 +4811,7 @@
if (klass->ShouldHaveEmbeddedImtAndVTable()) {
// Static fields come after the embedded tables.
base = mirror::Class::ComputeClassSize(true, klass->GetVTableDuringLinking()->GetLength(),
- 0, 0, 0);
+ 0, 0, 0, 0, 0);
}
field_offset = MemberOffset(base);
} else {
@@ -4726,6 +4828,8 @@
// we want a relatively stable order so that adding new fields
// minimizes disruption of C++ version such as Class and Method.
std::deque<mirror::ArtField*> grouped_and_sorted_fields;
+ const char* old_no_suspend_cause = Thread::Current()->StartAssertNoThreadSuspension(
+ "Naked ArtField references in deque");
for (size_t i = 0; i < num_fields; i++) {
mirror::ArtField* f = fields->Get(i);
CHECK(f != NULL) << PrettyClass(klass.Get());
@@ -4734,7 +4838,7 @@
std::sort(grouped_and_sorted_fields.begin(), grouped_and_sorted_fields.end(),
LinkFieldsComparator());
- // References should be at the front.
+ // References should be at the front, unless we need to pad.
size_t current_field = 0;
size_t num_reference_fields = 0;
for (; current_field < num_fields; current_field++) {
@@ -4751,44 +4855,21 @@
field_offset = MemberOffset(field_offset.Uint32Value() + sizeof(uint32_t));
}
- // Now we want to pack all of the double-wide fields together. If
- // we're not aligned, though, we want to shuffle one 32-bit field
- // into place. If we can't find one, we'll have to pad it.
- if (current_field != num_fields && !IsAligned<8>(field_offset.Uint32Value())) {
- for (size_t i = 0; i < grouped_and_sorted_fields.size(); i++) {
- mirror::ArtField* field = grouped_and_sorted_fields[i];
- Primitive::Type type = field->GetTypeAsPrimitiveType();
- CHECK(type != Primitive::kPrimNot) << PrettyField(field); // should be primitive types
- if (type == Primitive::kPrimLong || type == Primitive::kPrimDouble) {
- continue;
- }
- fields->Set<false>(current_field++, field);
- field->SetOffset(field_offset);
- // drop the consumed field
- grouped_and_sorted_fields.erase(grouped_and_sorted_fields.begin() + i);
- break;
- }
- // whether we found a 32-bit field for padding or not, we advance
- field_offset = MemberOffset(field_offset.Uint32Value() + sizeof(uint32_t));
- }
+ AlignFields<8>(current_field, num_fields, field_offset, fields, grouped_and_sorted_fields);
+ ShuffleForward<8>(current_field, num_fields, field_offset, fields, grouped_and_sorted_fields);
+ // No need for further alignment, start of object is 4-byte aligned.
+ ShuffleForward<4>(current_field, num_fields, field_offset, fields, grouped_and_sorted_fields);
+ ShuffleForward<2>(current_field, num_fields, field_offset, fields, grouped_and_sorted_fields);
+ ShuffleForward<1>(current_field, num_fields, field_offset, fields, grouped_and_sorted_fields);
+ CHECK(grouped_and_sorted_fields.empty()) << "Missed " << grouped_and_sorted_fields.size() <<
+ " fields.";
- // Alignment is good, shuffle any double-wide fields forward, and
- // finish assigning field offsets to all fields.
- DCHECK(current_field == num_fields || IsAligned<8>(field_offset.Uint32Value()))
- << PrettyClass(klass.Get());
- while (!grouped_and_sorted_fields.empty()) {
- mirror::ArtField* field = grouped_and_sorted_fields.front();
- grouped_and_sorted_fields.pop_front();
- Primitive::Type type = field->GetTypeAsPrimitiveType();
- CHECK(type != Primitive::kPrimNot) << PrettyField(field); // should be primitive types
- fields->Set<false>(current_field, field);
- field->SetOffset(field_offset);
- field_offset = MemberOffset(field_offset.Uint32Value() +
- ((type == Primitive::kPrimLong || type == Primitive::kPrimDouble)
- ? sizeof(uint64_t)
- : sizeof(uint32_t)));
- current_field++;
+ // Subclass expects superclass to be 4 byte aligned at end.
+ if (!IsAligned<4>(field_offset.Uint32Value())) {
+ field_offset = MemberOffset(RoundUp(field_offset.Uint32Value(), 4));
}
+ CHECK(IsAligned<4>(field_offset.Uint32Value()));
+ Thread::Current()->EndAssertNoThreadSuspension(old_no_suspend_cause);
// We lie to the GC about the java.lang.ref.Reference.referent field, so it doesn't scan it.
if (!is_static && klass->DescriptorEquals("Ljava/lang/ref/Reference;")) {