Record post startup methods in profile
Record post startup methods in the profile by visiting the methods
for all the loaded classes. This data will be used for layout
optimizations and generating the boot image profile.
The maps profile size doesn't seem to get larger after doing a basic
post launch use case. The change in profile size is hard to reliably
measure.
Verified by dumping a profile with profman and confirming that
there are post startup methods in the profile.
Added test to 595-profile-saving.
Did a test for maps to see how long we hold the class table lock. The
results are 4ms in the longest case. This should be short enough to
not cause jank if run every 20s or so.
Test: test-art-host
Bug: 36457259
Change-Id: I82e36957fcdc8d9e072c0193478bd1ef0ce09a67
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index de1fefd..00ae758 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -671,6 +671,10 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_);
+ // Visit all of the class loaders in the class linker.
+ void VisitClassLoaders(ClassLoaderVisitor* visitor) const
+ REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
+
struct DexCacheData {
// Construct an invalid data object.
DexCacheData()
@@ -720,9 +724,6 @@
static void DeleteClassLoader(Thread* self, const ClassLoaderData& data)
REQUIRES_SHARED(Locks::mutator_lock_);
- void VisitClassLoaders(ClassLoaderVisitor* visitor) const
- REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
-
void VisitClassesInternal(ClassVisitor* visitor)
REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 10dddae..556fe66 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -29,6 +29,7 @@
#include "base/stl_util.h"
#include "base/systrace.h"
#include "base/time_utils.h"
+#include "class_table-inl.h"
#include "compiler_filter.h"
#include "dex_reference_collection.h"
#include "gc/collector_type.h"
@@ -121,7 +122,7 @@
}
total_ms_of_sleep_ += options_.GetSaveResolvedClassesDelayMs();
}
- FetchAndCacheResolvedClassesAndMethods();
+ FetchAndCacheResolvedClassesAndMethods(/*startup*/ true);
// Loop for the profiled methods.
while (!ShuttingDown(self)) {
@@ -210,64 +211,6 @@
}
}
-using MethodReferenceCollection = DexReferenceCollection<uint16_t, ScopedArenaAllocatorAdapter>;
-using TypeReferenceCollection = DexReferenceCollection<dex::TypeIndex,
- ScopedArenaAllocatorAdapter>;
-
-// Get resolved methods that have a profile info or more than kStartupMethodSamples samples.
-// Excludes native methods and classes in the boot image.
-class GetClassesAndMethodsVisitor : public ClassVisitor {
- public:
- GetClassesAndMethodsVisitor(MethodReferenceCollection* hot_methods,
- MethodReferenceCollection* sampled_methods,
- TypeReferenceCollection* resolved_classes,
- uint32_t hot_method_sample_threshold,
- bool profile_boot_class_path)
- : hot_methods_(hot_methods),
- sampled_methods_(sampled_methods),
- resolved_classes_(resolved_classes),
- hot_method_sample_threshold_(hot_method_sample_threshold),
- profile_boot_class_path_(profile_boot_class_path) {}
-
- virtual bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (klass->IsProxyClass() ||
- klass->IsArrayClass() ||
- klass->IsPrimitive() ||
- !klass->IsResolved() ||
- klass->IsErroneousResolved() ||
- (!profile_boot_class_path_ && klass->GetClassLoader() == nullptr)) {
- return true;
- }
- CHECK(klass->GetDexCache() != nullptr) << klass->PrettyClass();
- resolved_classes_->AddReference(&klass->GetDexFile(), klass->GetDexTypeIndex());
- for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
- if (!method.IsNative()) {
- DCHECK(!method.IsProxyMethod());
- const uint16_t counter = method.GetCounter();
- // Mark startup methods as hot if they have more than hot_method_sample_threshold_ samples.
- // This means they will get compiled by the compiler driver.
- if (method.GetProfilingInfo(kRuntimePointerSize) != nullptr ||
- (method.GetAccessFlags() & kAccPreviouslyWarm) != 0 ||
- counter >= hot_method_sample_threshold_) {
- hot_methods_->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
- } else if (counter != 0) {
- sampled_methods_->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
- }
- } else {
- CHECK_EQ(method.GetCounter(), 0u);
- }
- }
- return true;
- }
-
- private:
- MethodReferenceCollection* const hot_methods_;
- MethodReferenceCollection* const sampled_methods_;
- TypeReferenceCollection* const resolved_classes_;
- uint32_t hot_method_sample_threshold_;
- const bool profile_boot_class_path_;
-};
-
class ScopedDefaultPriority {
public:
explicit ScopedDefaultPriority(pthread_t thread) : thread_(thread) {
@@ -282,7 +225,140 @@
const pthread_t thread_;
};
-void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() {
+// GetClassLoadersVisitor takes a snapshot of the class loaders and stores them in the out
+// class_loaders argument. Not affected by class unloading since there are no suspend points in
+// the caller.
+class GetClassLoadersVisitor : public ClassLoaderVisitor {
+ public:
+ explicit GetClassLoadersVisitor(VariableSizedHandleScope* hs,
+ std::vector<Handle<mirror::ClassLoader>>* class_loaders)
+ : hs_(hs),
+ class_loaders_(class_loaders) {}
+
+ void Visit(ObjPtr<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) OVERRIDE {
+ class_loaders_->push_back(hs_->NewHandle(class_loader));
+ }
+
+ private:
+ VariableSizedHandleScope* const hs_;
+ std::vector<Handle<mirror::ClassLoader>>* const class_loaders_;
+};
+
+// GetClassesVisitor takes a snapshot of the loaded classes that we may want to visit and stores
+// them in the out argument. Not affected by class unloading since there are no suspend points in
+// the caller.
+class GetClassesVisitor : public ClassVisitor {
+ public:
+ explicit GetClassesVisitor(bool profile_boot_class_path,
+ ScopedArenaVector<ObjPtr<mirror::Class>>* out)
+ : profile_boot_class_path_(profile_boot_class_path),
+ out_(out) {}
+
+ virtual bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (klass->IsProxyClass() ||
+ klass->IsArrayClass() ||
+ klass->IsPrimitive() ||
+ !klass->IsResolved() ||
+ klass->IsErroneousResolved() ||
+ (!profile_boot_class_path_ && klass->GetClassLoader() == nullptr)) {
+ return true;
+ }
+ out_->push_back(klass);
+ return true;
+ }
+
+ private:
+ const bool profile_boot_class_path_;
+ ScopedArenaVector<ObjPtr<mirror::Class>>* const out_;
+};
+
+using MethodReferenceCollection = DexReferenceCollection<uint16_t, ScopedArenaAllocatorAdapter>;
+using TypeReferenceCollection = DexReferenceCollection<dex::TypeIndex,
+ ScopedArenaAllocatorAdapter>;
+
+// Iterate over all of the loaded classes and visit each one. For each class, add it to the
+// resolved_classes out argument if startup is true.
+// Add methods to the hot_methods out argument if the number of samples is greater or equal to
+// hot_method_sample_threshold, add it to sampled_methods if it has at least one sample.
+static void SampleClassesAndExecutedMethods(pthread_t profiler_pthread,
+ bool profile_boot_class_path,
+ ScopedArenaAllocator* allocator,
+ uint32_t hot_method_sample_threshold,
+ bool startup,
+ TypeReferenceCollection* resolved_classes,
+ MethodReferenceCollection* hot_methods,
+ MethodReferenceCollection* sampled_methods) {
+ Thread* const self = Thread::Current();
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ // Restore profile saver thread priority during the GC critical section. This helps prevent
+ // priority inversions blocking the GC for long periods of time.
+ ScopedDefaultPriority sdp(profiler_pthread);
+ // Do ScopedGCCriticalSection before acquiring mutator lock to prevent the GC running and
+ // blocking threads during thread root flipping. Since the GC is a background thread, blocking it
+ // is not a problem.
+ ScopedObjectAccess soa(self);
+ gc::ScopedGCCriticalSection sgcs(self,
+ gc::kGcCauseProfileSaver,
+ gc::kCollectorTypeCriticalSection);
+ VariableSizedHandleScope hs(soa.Self());
+ std::vector<Handle<mirror::ClassLoader>> class_loaders;
+ if (profile_boot_class_path) {
+ // First add the boot class loader since visit classloaders doesn't visit it.
+ class_loaders.push_back(hs.NewHandle<mirror::ClassLoader>(nullptr));
+ }
+ GetClassLoadersVisitor class_loader_visitor(&hs, &class_loaders);
+ {
+ // Read the class loaders into a temporary array to prevent contention problems on the
+ // class_linker_classes_lock.
+ ScopedTrace trace2("Get class loaders");
+ ReaderMutexLock mu(soa.Self(), *Locks::classlinker_classes_lock_);
+ class_linker->VisitClassLoaders(&class_loader_visitor);
+ }
+ ScopedArenaVector<ObjPtr<mirror::Class>> classes(allocator->Adapter());
+ for (Handle<mirror::ClassLoader> class_loader : class_loaders) {
+ ClassTable* table = class_linker->ClassTableForClassLoader(class_loader.Get());
+ if (table == nullptr) {
+ // If the class loader has not loaded any classes, it may have a null table.
+ continue;
+ }
+ GetClassesVisitor get_classes_visitor(profile_boot_class_path, &classes);
+ {
+ // Collect the classes into a temporary array to prevent lock contention on the class
+ // table lock. We want to avoid blocking class loading in other threads as much as
+ // possible.
+ ScopedTrace trace3("Visiting class table");
+ table->Visit(get_classes_visitor);
+ }
+ for (ObjPtr<mirror::Class> klass : classes) {
+ if (startup) {
+ // We only record classes for the startup case. This may change in the future.
+ resolved_classes->AddReference(&klass->GetDexFile(), klass->GetDexTypeIndex());
+ }
+ // Visit all of the methods in the class to see which ones were executed.
+ for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
+ if (!method.IsNative()) {
+ DCHECK(!method.IsProxyMethod());
+ const uint16_t counter = method.GetCounter();
+ // Mark startup methods as hot if they have more than hot_method_sample_threshold
+ // samples. This means they will get compiled by the compiler driver.
+ if (method.GetProfilingInfo(kRuntimePointerSize) != nullptr ||
+ (method.GetAccessFlags() & kAccPreviouslyWarm) != 0 ||
+ counter >= hot_method_sample_threshold) {
+ hot_methods->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
+ } else if (counter != 0) {
+ sampled_methods->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
+ }
+ } else {
+ CHECK_EQ(method.GetCounter(), 0u);
+ }
+ }
+ }
+ classes.clear();
+ }
+}
+
+void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) {
ScopedTrace trace(__PRETTY_FUNCTION__);
const uint64_t start_time = NanoTime();
@@ -294,34 +370,25 @@
ArenaStack stack(runtime->GetArenaPool());
ScopedArenaAllocator allocator(&stack);
MethodReferenceCollection hot_methods(allocator.Adapter(), allocator.Adapter());
- MethodReferenceCollection startup_methods(allocator.Adapter(), allocator.Adapter());
+ MethodReferenceCollection sampled_methods(allocator.Adapter(), allocator.Adapter());
TypeReferenceCollection resolved_classes(allocator.Adapter(), allocator.Adapter());
const bool is_low_ram = Runtime::Current()->GetHeap()->IsLowMemoryMode();
- const size_t hot_threshold = options_.GetHotStartupMethodSamples(is_low_ram);
pthread_t profiler_pthread;
{
MutexLock mu(self, *Locks::profiler_lock_);
profiler_pthread = profiler_pthread_;
}
- {
- // Restore profile saver thread priority during the GC critical section. This helps prevent
- // priority inversions blocking the GC for long periods of time.
- ScopedDefaultPriority sdp(profiler_pthread);
- ScopedObjectAccess soa(self);
- gc::ScopedGCCriticalSection sgcs(self,
- gc::kGcCauseProfileSaver,
- gc::kCollectorTypeCriticalSection);
- {
- ScopedTrace trace2("Get hot methods");
- GetClassesAndMethodsVisitor visitor(&hot_methods,
- &startup_methods,
- &resolved_classes,
- hot_threshold,
- options_.GetProfileBootClassPath());
- runtime->GetClassLinker()->VisitClasses(&visitor);
- }
- }
-
+ const uint32_t hot_method_sample_threshold = startup ?
+ options_.GetHotStartupMethodSamples(is_low_ram) :
+ std::numeric_limits<uint32_t>::max();
+ SampleClassesAndExecutedMethods(profiler_pthread,
+ options_.GetProfileBootClassPath(),
+ &allocator,
+ hot_method_sample_threshold,
+ startup,
+ &resolved_classes,
+ &hot_methods,
+ &sampled_methods);
MutexLock mu(self, *Locks::profiler_lock_);
uint64_t total_number_of_profile_entries_cached = 0;
using Hotness = ProfileCompilationInfo::MethodHotness;
@@ -329,9 +396,12 @@
for (const auto& it : tracked_dex_base_locations_) {
std::set<DexCacheResolvedClasses> resolved_classes_for_location;
const std::string& filename = it.first;
- auto info_it = profile_cache_.Put(
- filename,
- new ProfileCompilationInfo(Runtime::Current()->GetArenaPool()));
+ auto info_it = profile_cache_.find(filename);
+ if (info_it == profile_cache_.end()) {
+ info_it = profile_cache_.Put(
+ filename,
+ new ProfileCompilationInfo(Runtime::Current()->GetArenaPool()));
+ }
ProfileCompilationInfo* cached_info = info_it->second;
const std::set<std::string>& locations = it.second;
@@ -339,18 +409,20 @@
const DexFile* const dex_file = pair.first;
if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
const MethodReferenceCollection::IndexVector& indices = pair.second;
+ uint8_t flags = Hotness::kFlagHot;
+ flags |= startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup;
cached_info->AddMethodsForDex(
- static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup),
+ static_cast<Hotness::Flag>(flags),
dex_file,
indices.begin(),
indices.end());
}
}
- for (const auto& pair : startup_methods.GetMap()) {
+ for (const auto& pair : sampled_methods.GetMap()) {
const DexFile* const dex_file = pair.first;
if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
const MethodReferenceCollection::IndexVector& indices = pair.second;
- cached_info->AddMethodsForDex(Hotness::kFlagStartup,
+ cached_info->AddMethodsForDex(startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup,
dex_file,
indices.begin(),
indices.end());
@@ -375,8 +447,9 @@
max_number_of_profile_entries_cached_,
total_number_of_profile_entries_cached);
VLOG(profiler) << "Profile saver recorded " << hot_methods.NumReferences() << " hot methods and "
- << startup_methods.NumReferences() << " startup methods with threshold "
- << hot_threshold << " in " << PrettyDuration(NanoTime() - start_time);
+ << sampled_methods.NumReferences() << " sampled methods with threshold "
+ << hot_method_sample_threshold << " in "
+ << PrettyDuration(NanoTime() - start_time);
}
bool ProfileSaver::ProcessProfilingInfo(bool force_save, /*out*/uint16_t* number_of_new_methods) {
@@ -397,6 +470,10 @@
*number_of_new_methods = 0;
}
+ // We only need to do this once, not once per dex location.
+ // TODO: Figure out a way to only do it when stuff has changed? It takes 30-50ms.
+ FetchAndCacheResolvedClassesAndMethods(/*startup*/ false);
+
for (const auto& it : tracked_locations) {
if (!force_save && ShuttingDown(Thread::Current())) {
// The ProfileSaver is in shutdown mode, meaning a stop request was made and
@@ -442,6 +519,7 @@
total_number_of_skipped_writes_++;
continue;
}
+
if (number_of_new_methods != nullptr) {
*number_of_new_methods =
std::max(static_cast<uint16_t>(delta_number_of_methods),
@@ -473,11 +551,12 @@
total_number_of_failed_writes_++;
}
}
- // Trim the maps to madvise the pages used for profile info.
- // It is unlikely we will need them again in the near feature.
- Runtime::Current()->GetArenaPool()->TrimMaps();
}
+ // Trim the maps to madvise the pages used for profile info.
+ // It is unlikely we will need them again in the near feature.
+ Runtime::Current()->GetArenaPool()->TrimMaps();
+
return profile_file_saved;
}
@@ -713,16 +792,15 @@
}
}
-bool ProfileSaver::HasSeenMethod(const std::string& profile,
- const DexFile* dex_file,
- uint16_t method_idx) {
+bool ProfileSaver::HasSeenMethod(const std::string& profile, bool hot, MethodReference ref) {
MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
if (instance_ != nullptr) {
ProfileCompilationInfo info(Runtime::Current()->GetArenaPool());
if (!info.Load(profile, /*clear_if_invalid*/false)) {
return false;
}
- return info.GetMethodHotness(MethodReference(dex_file, method_idx)).IsInProfile();
+ ProfileCompilationInfo::MethodHotness hotness = info.GetMethodHotness(ref);
+ return hot ? hotness.IsHot() : hotness.IsInProfile();
}
return false;
}
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index 01d72fe..ce8233b 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -19,6 +19,7 @@
#include "base/mutex.h"
#include "jit_code_cache.h"
+#include "method_reference.h"
#include "profile_compilation_info.h"
#include "profile_saver_options.h"
#include "safe_map.h"
@@ -55,10 +56,8 @@
// For testing or manual purposes (SIGUSR1).
static void ForceProcessProfiles();
- // Just for testing purpose.
- static bool HasSeenMethod(const std::string& profile,
- const DexFile* dex_file,
- uint16_t method_idx);
+ // Just for testing purposes.
+ static bool HasSeenMethod(const std::string& profile, bool hot, MethodReference ref);
private:
ProfileSaver(const ProfileSaverOptions& options,
@@ -97,7 +96,7 @@
// Fetches the current resolved classes and methods from the ClassLinker and stores them in the
// profile_cache_ for later save.
- void FetchAndCacheResolvedClassesAndMethods();
+ void FetchAndCacheResolvedClassesAndMethods(bool startup);
void DumpInfo(std::ostream& os);
diff --git a/test/595-profile-saving/profile-saving.cc b/test/595-profile-saving/profile-saving.cc
index 0bdbade..b2b7ef2 100644
--- a/test/595-profile-saving/profile-saving.cc
+++ b/test/595-profile-saving/profile-saving.cc
@@ -45,8 +45,9 @@
ProfileSaver::ForceProcessProfiles();
}
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_presentInProfile(JNIEnv* env,
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_profileHasMethod(JNIEnv* env,
jclass,
+ jboolean hot,
jstring filename,
jobject method) {
ScopedUtfChars filename_chars(env, filename);
@@ -55,8 +56,9 @@
ObjPtr<mirror::Executable> exec = soa.Decode<mirror::Executable>(method);
ArtMethod* art_method = exec->GetArtMethod();
return ProfileSaver::HasSeenMethod(std::string(filename_chars.c_str()),
- art_method->GetDexFile(),
- art_method->GetDexMethodIndex());
+ hot != JNI_FALSE,
+ MethodReference(art_method->GetDexFile(),
+ art_method->GetDexMethodIndex()));
}
} // namespace
diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java
index 18c0598..197c4e7 100644
--- a/test/595-profile-saving/src/Main.java
+++ b/test/595-profile-saving/src/Main.java
@@ -42,6 +42,14 @@
System.out.println("Class loader does not match boot class");
}
testAddMethodToProfile(file, bootMethod);
+
+ // Test a sampled method that is only warm and not hot.
+ Method reflectMethod = Main.class.getDeclaredMethod("testReflectionInvoke");
+ reflectMethod.invoke(null);
+ testSampledMethodInProfile(file, reflectMethod);
+ if (staticObj == null) {
+ throw new AssertionError("Object was not set");
+ }
} finally {
if (file != null) {
file.delete();
@@ -49,23 +57,38 @@
}
}
+ static Object staticObj = null;
+
+ static void testReflectionInvoke() {
+ staticObj = new Object();
+ }
+
static void testAddMethodToProfile(File file, Method m) {
// Make sure we have a profile info for this method without the need to loop.
ensureProfilingInfo(m);
- // Make sure the profile gets saved.
+ // Make sure the profile gets processed.
ensureProfileProcessing();
// Verify that the profile was saved and contains the method.
- if (!presentInProfile(file.getPath(), m)) {
+ if (!profileHasMethod(true, file.getPath(), m)) {
throw new RuntimeException("Method with index " + m + " not in the profile");
}
}
+ static void testSampledMethodInProfile(File file, Method m) {
+ // Make sure the profile gets processed.
+ ensureProfileProcessing();
+ // Verify that the profile was saved and contains the method.
+ if (!profileHasMethod(false, file.getPath(), m)) {
+ throw new RuntimeException("Method with index " + m + " not sampled in the profile");
+ }
+ }
+
// Ensure a method has a profiling info.
public static native void ensureProfilingInfo(Method method);
// Ensures the profile saver does its usual processing.
public static native void ensureProfileProcessing();
- // Checks if the profiles saver knows about the method.
- public static native boolean presentInProfile(String profile, Method method);
+ // Checks if the profile saver has the method as a warm/sampled method.
+ public static native boolean profileHasMethod(boolean hot, String profile, Method method);
private static final String TEMP_FILE_NAME_PREFIX = "dummy";
private static final String TEMP_FILE_NAME_SUFFIX = "-file";