Merge "Record post startup methods in profile"
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";