Skip to content

Commit

Permalink
Reland "Allocate dex cache arrays at startup."
Browse files Browse the repository at this point in the history
This reverts commit cc97f11.

Reason for revert: be more selective when using full arrays.

Test: test.py

Change-Id: If941e81849d9e3b2c4ddcc03e46fb8442a608c82
  • Loading branch information
Nicolas Geoffray committed Oct 28, 2022
1 parent f3df7be commit 1316d3a
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 15 deletions.
5 changes: 3 additions & 2 deletions runtime/jit/profile_saver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,18 @@ void ProfileSaver::Run() {
{
MutexLock mu(self, wait_lock_);

const uint64_t end_time = NanoTime() + MsToNs(force_early_first_save
const uint64_t sleep_time = MsToNs(force_early_first_save
? options_.GetMinFirstSaveMs()
: options_.GetSaveResolvedClassesDelayMs());
const uint64_t end_time = NanoTime() + sleep_time;
while (!Runtime::Current()->GetStartupCompleted()) {
const uint64_t current_time = NanoTime();
if (current_time >= end_time) {
break;
}
period_condition_.TimedWait(self, NsToMs(end_time - current_time), 0);
}
total_ms_of_sleep_ += options_.GetSaveResolvedClassesDelayMs();
total_ms_of_sleep_ += sleep_time;
}
// Tell the runtime that startup is completed if it has not already been notified.
// TODO: We should use another thread to do this in case the profile saver is not running.
Expand Down
1 change: 0 additions & 1 deletion runtime/linear_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class TrackingHeader final {

std::ostream& operator<<(std::ostream& os, LinearAllocKind value);

// TODO: Support freeing if we add class unloading.
class LinearAlloc {
public:
static constexpr size_t kAlignment = 8u;
Expand Down
12 changes: 9 additions & 3 deletions runtime/mirror/dex_cache-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ static void InitializeArray(T*) {
}

template<typename T>
T* DexCache::AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kind) {
T* DexCache::AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kind, bool startup) {
Thread* self = Thread::Current();
mirror::DexCache* dex_cache = this;
if (gUseReadBarrier && self->GetIsGcMarking()) {
Expand All @@ -63,8 +63,14 @@ T* DexCache::AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kin
dex_cache = reinterpret_cast<DexCache*>(ReadBarrier::Mark(this));
}
// DON'T USE 'this' from now on.
ClassLinker* linker = Runtime::Current()->GetClassLinker();
LinearAlloc* alloc = linker->GetOrCreateAllocatorForClassLoader(GetClassLoader());
Runtime* runtime = Runtime::Current();
// Note: in the 1002-notify-startup test, the startup linear alloc can become null
// concurrently, even if the runtime is marked at startup. Therefore we should only
// fetch it once here.
LinearAlloc* startup_linear_alloc = runtime->GetStartupLinearAlloc();
LinearAlloc* alloc = (startup && startup_linear_alloc != nullptr)
? startup_linear_alloc
: runtime->GetClassLinker()->GetOrCreateAllocatorForClassLoader(GetClassLoader());
MutexLock mu(self, *Locks::dex_cache_lock_); // Avoid allocation by multiple threads.
T* array = dex_cache->GetFieldPtr64<T*>(obj_offset);
if (array != nullptr) {
Expand Down
50 changes: 50 additions & 0 deletions runtime/mirror/dex_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "class_linker.h"
#include "gc/accounting/card_table-inl.h"
#include "gc/heap.h"
#include "jit/profile_saver.h"
#include "linear_alloc.h"
#include "oat_file.h"
#include "object-inl.h"
Expand Down Expand Up @@ -165,5 +166,54 @@ ObjPtr<ClassLoader> DexCache::GetClassLoader() {
return GetFieldObject<ClassLoader>(OFFSET_OF_OBJECT_MEMBER(DexCache, class_loader_));
}

bool DexCache::ShouldAllocateFullArrayAtStartup() {
Runtime* runtime = Runtime::Current();
if (runtime->IsAotCompiler()) {
// To save on memory in dex2oat, we don't allocate full arrays by default.
return false;
}

if (runtime->GetStartupCompleted()) {
// We only allocate full arrays during app startup.
return false;
}

if (GetClassLoader() == nullptr) {
// Only allocate full array for app dex files (also note that for
// multi-image, the `GetCompilerFilter` call below does not work for
// non-primary oat files).
return false;
}

const OatDexFile* oat_dex_file = GetDexFile()->GetOatDexFile();
if (oat_dex_file != nullptr &&
CompilerFilter::IsAotCompilationEnabled(oat_dex_file->GetOatFile()->GetCompilerFilter())) {
// We only allocate full arrays for dex files where we do not have
// compilation.
return false;
}

if (!ProfileSaver::IsStarted()) {
// Only allocate full arrays if the profile saver is running: if the app
// does not call `reportFullyDrawn`, then only the profile saver will notify
// that the app has eventually started.
return false;
}

return true;
}

void DexCache::UnlinkStartupCaches() {
if (GetDexFile() == nullptr) {
// Unused dex cache.
return;
}
UnlinkStringsArrayIfStartup();
UnlinkResolvedFieldsArrayIfStartup();
UnlinkResolvedMethodsArrayIfStartup();
UnlinkResolvedTypesArrayIfStartup();
UnlinkResolvedMethodTypesArrayIfStartup();
}

} // namespace mirror
} // namespace art
31 changes: 26 additions & 5 deletions runtime/mirror/dex_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ class MANAGED DexCache final : public Object {
void VisitNativeRoots(const Visitor& visitor)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);

// Sets null to dex cache array fields which were allocated with the startup
// allocator.
void UnlinkStartupCaches() REQUIRES_SHARED(Locks::mutator_lock_);

// NOLINTBEGIN(bugprone-macro-parentheses)
#define DEFINE_ARRAY(name, array_kind, getter_setter, type, ids, alloc_kind) \
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
Expand All @@ -380,10 +384,10 @@ class MANAGED DexCache final : public Object {
static constexpr MemberOffset getter_setter ##Offset() { \
return OFFSET_OF_OBJECT_MEMBER(DexCache, name); \
} \
array_kind* Allocate ##getter_setter() \
array_kind* Allocate ##getter_setter(bool startup = false) \
REQUIRES_SHARED(Locks::mutator_lock_) { \
return reinterpret_cast<array_kind*>(AllocArray<type>( \
getter_setter ##Offset(), GetDexFile()->ids(), alloc_kind)); \
getter_setter ##Offset(), GetDexFile()->ids(), alloc_kind, startup)); \
} \
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
size_t Num ##getter_setter() REQUIRES_SHARED(Locks::mutator_lock_) { \
Expand Down Expand Up @@ -443,8 +447,9 @@ class MANAGED DexCache final : public Object {
} else { \
auto* pairs = Get ##getter_setter(); \
if (pairs == nullptr) { \
if (GetDexFile()->ids() <= pair_size) { \
array = Allocate ##getter_setter ##Array(); \
bool should_allocate_full_array = ShouldAllocateFullArray(GetDexFile()->ids(), pair_size); \
if (ShouldAllocateFullArrayAtStartup() || should_allocate_full_array) { \
array = Allocate ##getter_setter ##Array(!should_allocate_full_array); \
array->Set(index, resolved); \
} else { \
pairs = Allocate ##getter_setter(); \
Expand All @@ -454,6 +459,12 @@ class MANAGED DexCache final : public Object {
pairs->Set(index, resolved); \
} \
} \
} \
void Unlink ##getter_setter ##ArrayIfStartup() \
REQUIRES_SHARED(Locks::mutator_lock_) { \
if (!ShouldAllocateFullArray(GetDexFile()->ids(), pair_size)) { \
Set ##getter_setter ##Array(nullptr) ; \
} \
}

DEFINE_ARRAY(resolved_call_sites_,
Expand Down Expand Up @@ -523,7 +534,7 @@ class MANAGED DexCache final : public Object {
private:
// Allocate new array in linear alloc and save it in the given fields.
template<typename T>
T* AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kind)
T* AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kind, bool startup = false)
REQUIRES_SHARED(Locks::mutator_lock_);

// Visit instance fields of the dex cache as well as its associated arrays.
Expand All @@ -534,6 +545,16 @@ class MANAGED DexCache final : public Object {
void VisitReferences(ObjPtr<Class> klass, const Visitor& visitor)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);

// Returns whether we should allocate a full array given the current state of
// the runtime and oat files.
bool ShouldAllocateFullArrayAtStartup() REQUIRES_SHARED(Locks::mutator_lock_);

// Returns whether we should allocate a full array given the number of
// elements.
static bool ShouldAllocateFullArray(size_t number_of_elements, size_t dex_cache_size) {
return number_of_elements <= dex_cache_size;
}

HeapReference<ClassLoader> class_loader_;
HeapReference<String> location_;

Expand Down
41 changes: 37 additions & 4 deletions runtime/runtime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ Runtime::~Runtime() {
// Destroy allocators before shutting down the MemMap because they may use it.
java_vm_.reset();
linear_alloc_.reset();
startup_linear_alloc_.reset();
linear_alloc_arena_pool_.reset();
arena_pool_.reset();
jit_arena_pool_.reset();
Expand Down Expand Up @@ -1748,6 +1749,7 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
linear_alloc_arena_pool_.reset(new MemMapArenaPool(low_4gb));
}
linear_alloc_.reset(CreateLinearAlloc());
startup_linear_alloc_.reset(CreateLinearAlloc());

small_irt_allocator_ = new SmallIrtAllocator();

Expand Down Expand Up @@ -2749,7 +2751,8 @@ void Runtime::RegisterAppInfo(const std::string& package_name,
return;
}
if (!OS::FileExists(profile_output_filename.c_str(), /*check_file_type=*/ false)) {
LOG(WARNING) << "JIT profile information will not be recorded: profile file does not exist.";
LOG(WARNING) << "JIT profile information will not be recorded: profile file does not exist: "
<< profile_output_filename;
return;
}
if (code_paths.empty()) {
Expand Down Expand Up @@ -3306,18 +3309,40 @@ void Runtime::ResetStartupCompleted() {
startup_completed_.store(false, std::memory_order_seq_cst);
}

class UnlinkStartupDexCacheVisitor : public DexCacheVisitor {
public:
void Visit(ObjPtr<mirror::DexCache> dex_cache)
REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
dex_cache->UnlinkStartupCaches();
}
};

class Runtime::NotifyStartupCompletedTask : public gc::HeapTask {
public:
NotifyStartupCompletedTask() : gc::HeapTask(/*target_run_time=*/ NanoTime()) {}

void Run(Thread* self) override {
VLOG(startup) << "NotifyStartupCompletedTask running";
Runtime* const runtime = Runtime::Current();
// Fetch the startup linear alloc before the checkpoint to play nice with
// 1002-notify-startup test which resets the startup state.
std::unique_ptr<LinearAlloc> startup_linear_alloc(runtime->ReleaseStartupLinearAlloc());
{
ScopedTrace trace("Releasing app image spaces metadata");
ScopedTrace trace("Releasing dex caches and app image spaces metadata");
ScopedObjectAccess soa(Thread::Current());
// Request empty checkpoints to make sure no threads are accessing the image space metadata
// section when we madvise it. Use GC exclusion to prevent deadlocks that may happen if

{
// Unlink dex caches that were allocated with the startup linear alloc.
UnlinkStartupDexCacheVisitor visitor;
ReaderMutexLock mu(self, *Locks::dex_lock_);
runtime->GetClassLinker()->VisitDexCaches(&visitor);
}

// Request empty checkpoints to make sure no threads are:
// - accessing the image space metadata section when we madvise it
// - accessing dex caches when we free them
//
// Use GC exclusion to prevent deadlocks that may happen if
// multiple threads are attempting to run empty checkpoints at the same time.
{
// Avoid using ScopedGCCriticalSection since that does not allow thread suspension. This is
Expand All @@ -3328,6 +3353,7 @@ class Runtime::NotifyStartupCompletedTask : public gc::HeapTask {
gc::kCollectorTypeCriticalSection);
runtime->GetThreadList()->RunEmptyCheckpoint();
}

for (gc::space::ContinuousSpace* space : runtime->GetHeap()->GetContinuousSpaces()) {
if (space->IsImageSpace()) {
gc::space::ImageSpace* image_space = space->AsImageSpace();
Expand All @@ -3343,6 +3369,13 @@ class Runtime::NotifyStartupCompletedTask : public gc::HeapTask {
ScopedTrace trace2("Delete thread pool");
runtime->DeleteThreadPool();
}

{
// We know that after the checkpoint, there is no thread that can hold
// the startup linear alloc, so it's safe to delete it now.
ScopedTrace trace2("Delete startup linear alloc");
startup_linear_alloc.reset();
}
}
};

Expand Down
12 changes: 12 additions & 0 deletions runtime/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,10 @@ class Runtime {
return linear_alloc_.get();
}

LinearAlloc* GetStartupLinearAlloc() {
return startup_linear_alloc_.get();
}

jit::JitOptions* GetJITOptions() {
return jit_options_.get();
}
Expand Down Expand Up @@ -1062,6 +1066,10 @@ class Runtime {
ThreadPool* const thread_pool_;
};

LinearAlloc* ReleaseStartupLinearAlloc() {
return startup_linear_alloc_.release();
}

bool LoadAppImageStartupCache() const {
return load_app_image_startup_cache_;
}
Expand Down Expand Up @@ -1278,6 +1286,10 @@ class Runtime {
// Shared linear alloc for now.
std::unique_ptr<LinearAlloc> linear_alloc_;

// Linear alloc used for allocations during startup. Will be deleted after
// startup.
std::unique_ptr<LinearAlloc> startup_linear_alloc_;

// The number of spins that are done before thread suspension is used to forcibly inflate.
size_t max_spins_before_thin_lock_inflation_;
MonitorList* monitor_list_;
Expand Down

0 comments on commit 1316d3a

Please sign in to comment.