Improve ProfileSaver to cache data and do minimal I/O

This CLs introducing caching to further optimize the I/O performed by
the ProfileSaver. The cache stats are also recorded.

Resolved classes are captured and cached after 2 seconds but written
later during application lifetime.

Methods are also cached and the write got smarter to avoid
reading/writing them if it's not needed.

On non scientific experiments the size of the cache reaches just a few
hundreds uint16_t values kept in set, so its impact is minimal.

In terms of how much data we write. In the same non scientific
experiments this reduces the total bytes written by at least 2-3x. In
the first few minutes of after the boot gmscore writes ~3KB (down from
9kb) and quicksearchlauncher writes ~20KB (down from 40KB).

Bug: 27600652

(cherry picked from commit 85f7bf3bbfa17d65ff77e3e826b5f7aff45838a9)

Change-Id: I6ecb04ce81b281d1c1538060dbbaeeeb08906775
diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc
index 58e8a3a..ac18657 100644
--- a/profman/profile_assistant.cc
+++ b/profman/profile_assistant.cc
@@ -54,7 +54,7 @@
 
   for (size_t i = 0; i < new_info.size(); i++) {
     // Merge all data into a single object.
-    if (!info.Load(new_info[i])) {
+    if (!info.MergeWith(new_info[i])) {
       LOG(WARNING) << "Could not merge profile data at index " << i;
       return kErrorBadProfiles;
     }
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index b0d5df2..157ffc4 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -102,8 +102,8 @@
   ASSERT_TRUE(result.Load(reference_profile_fd));
 
   ProfileCompilationInfo expected;
-  ASSERT_TRUE(expected.Load(info1));
-  ASSERT_TRUE(expected.Load(info2));
+  ASSERT_TRUE(expected.MergeWith(info1));
+  ASSERT_TRUE(expected.MergeWith(info2));
   ASSERT_TRUE(expected.Equals(result));
 
   // The information from profiles must remain the same.
@@ -145,9 +145,9 @@
   ASSERT_TRUE(result.Load(reference_profile_fd));
 
   ProfileCompilationInfo expected;
-  ASSERT_TRUE(expected.Load(info1));
-  ASSERT_TRUE(expected.Load(info2));
-  ASSERT_TRUE(expected.Load(reference_info));
+  ASSERT_TRUE(expected.MergeWith(info1));
+  ASSERT_TRUE(expected.MergeWith(info2));
+  ASSERT_TRUE(expected.MergeWith(reference_info));
   ASSERT_TRUE(expected.Equals(result));
 
   // The information from profiles must remain the same.
diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/offline_profiling_info.cc
index e805ce1..e864d87 100644
--- a/runtime/jit/offline_profiling_info.cc
+++ b/runtime/jit/offline_profiling_info.cc
@@ -49,6 +49,79 @@
   }
 }
 
+bool ProfileCompilationInfo::AddMethodsAndClasses(
+    const std::vector<ArtMethod*>& methods,
+    const std::set<DexCacheResolvedClasses>& resolved_classes) {
+  ScopedObjectAccess soa(Thread::Current());
+  for (ArtMethod* method : methods) {
+    const DexFile* dex_file = method->GetDexFile();
+    if (!AddMethodIndex(GetProfileDexFileKey(dex_file->GetLocation()),
+                        dex_file->GetLocationChecksum(),
+                        method->GetDexMethodIndex())) {
+      return false;
+    }
+  }
+  for (const DexCacheResolvedClasses& dex_cache : resolved_classes) {
+    if (!AddResolvedClasses(dex_cache)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ProfileCompilationInfo::MergeAndSave(const std::string& filename, uint64_t* bytes_written) {
+  ScopedTrace trace(__PRETTY_FUNCTION__);
+  ScopedFlock flock;
+  std::string error;
+  if (!flock.Init(filename.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC, /* block */ false, &error)) {
+    LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error;
+    return false;
+  }
+
+  int fd = flock.GetFile()->Fd();
+
+  // Load the file but keep a copy around to be able to infer if the content has changed.
+  ProfileCompilationInfo fileInfo;
+  if (!fileInfo.Load(fd)) {
+    LOG(WARNING) << "Could not load previous profile data from file " << filename;
+    return false;
+  }
+
+  // Merge the content of file into the current object.
+  if (!MergeWith(fileInfo)) {
+    LOG(WARNING) << "Could not merge previous profile data from file " << filename;
+  }
+
+  // If after the merge we have the same data as what is the file there's no point
+  // in actually doing the write. The file will be exactly the same as before.
+  if (Equals(fileInfo)) {
+    if (bytes_written != nullptr) {
+      *bytes_written = 0;
+    }
+    return true;
+  }
+
+  // We need to clear the data because we don't support append to the profiles yet.
+  if (!flock.GetFile()->ClearContent()) {
+    PLOG(WARNING) << "Could not clear profile file: " << filename;
+    return false;
+  }
+
+  // This doesn't need locking because we are trying to lock the file for exclusive
+  // access and fail immediately if we can't.
+  bool result = Save(fd);
+  if (result) {
+    VLOG(profiler) << "Successfully saved profile info to " << filename
+        << " Size: " << GetFileSizeBytes(filename);
+    if (bytes_written != nullptr) {
+      *bytes_written = GetFileSizeBytes(filename);
+    }
+  } else {
+    VLOG(profiler) << "Failed to save profile info to " << filename;
+  }
+  return result;
+}
+
 bool ProfileCompilationInfo::SaveProfilingInfo(
     const std::string& filename,
     const std::vector<ArtMethod*>& methods,
@@ -62,54 +135,16 @@
     return true;
   }
 
-  ScopedTrace trace(__PRETTY_FUNCTION__);
-  ScopedFlock flock;
-  std::string error;
-  if (!flock.Init(filename.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC, /* block */ false, &error)) {
-    LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error;
-    return false;
-  }
-
-  int fd = flock.GetFile()->Fd();
-
   ProfileCompilationInfo info;
-  if (!info.Load(fd)) {
-    LOG(WARNING) << "Could not load previous profile data from file " << filename;
-    return false;
-  }
-  {
-    ScopedObjectAccess soa(Thread::Current());
-    for (ArtMethod* method : methods) {
-      const DexFile* dex_file = method->GetDexFile();
-      if (!info.AddMethodIndex(GetProfileDexFileKey(dex_file->GetLocation()),
-                               dex_file->GetLocationChecksum(),
-                               method->GetDexMethodIndex())) {
-        return false;
-      }
-    }
-    for (const DexCacheResolvedClasses& dex_cache : resolved_classes) {
-      info.AddResolvedClasses(dex_cache);
-    }
-  }
-
-  if (!flock.GetFile()->ClearContent()) {
-    PLOG(WARNING) << "Could not clear profile file: " << filename;
-    return false;
-  }
-
-  // This doesn't need locking because we are trying to lock the file for exclusive
-  // access and fail immediately if we can't.
-  bool result = info.Save(fd);
-  if (result) {
-    VLOG(profiler) << "Successfully saved profile info to " << filename
-        << " Size: " << GetFileSizeBytes(filename);
+  if (!info.AddMethodsAndClasses(methods, resolved_classes)) {
+    LOG(WARNING) << "Checksum mismatch when processing methods and resolved classes for "
+        << filename;
     if (bytes_written != nullptr) {
-      *bytes_written = GetFileSizeBytes(filename);
+      *bytes_written = 0;
     }
-  } else {
-    VLOG(profiler) << "Failed to save profile info to " << filename;
+    return false;
   }
-  return result;
+  return info.MergeAndSave(filename, bytes_written);
 }
 
 static bool WriteToFile(int fd, const std::ostringstream& os) {
@@ -341,7 +376,7 @@
   return true;
 }
 
-bool ProfileCompilationInfo::Load(const ProfileCompilationInfo& other) {
+bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) {
   for (const auto& other_it : other.info_) {
     const std::string& other_dex_location = other_it.first;
     const DexFileData& other_dex_data = other_it.second;
@@ -394,6 +429,14 @@
   return total;
 }
 
+uint32_t ProfileCompilationInfo::GetNumberOfResolvedClasses() const {
+  uint32_t total = 0;
+  for (const auto& it : info_) {
+    total += it.second.class_set.size();
+  }
+  return total;
+}
+
 std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* dex_files,
                                              bool print_full_dex_location) const {
   std::ostringstream os;
@@ -449,4 +492,10 @@
   return ret;
 }
 
+void ProfileCompilationInfo::ClearResolvedClasses() {
+  for (auto& pair : info_) {
+    pair.second.class_set.clear();
+  }
+}
+
 }  // namespace art
diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/offline_profiling_info.h
index fb07f8c..2c819f1 100644
--- a/runtime/jit/offline_profiling_info.h
+++ b/runtime/jit/offline_profiling_info.h
@@ -49,14 +49,23 @@
                                 const std::set<DexCacheResolvedClasses>& resolved_classes,
                                 uint64_t* bytes_written = nullptr);
 
+  // Add the given methods and classes to the current profile object.
+  bool AddMethodsAndClasses(const std::vector<ArtMethod*>& methods,
+                            const std::set<DexCacheResolvedClasses>& resolved_classes);
   // Loads profile information from the given file descriptor.
   bool Load(int fd);
-  // Loads the data from another ProfileCompilationInfo object.
-  bool Load(const ProfileCompilationInfo& info);
+  // Merge the data from another ProfileCompilationInfo into the current object.
+  bool MergeWith(const ProfileCompilationInfo& info);
   // Saves the profile data to the given file descriptor.
   bool Save(int fd);
+  // Loads and merges profile information from the given file into the current
+  // object and tries to save it back to disk.
+  bool MergeAndSave(const std::string& filename, uint64_t* bytes_written);
+
   // Returns the number of methods that were profiled.
   uint32_t GetNumberOfMethods() const;
+  // Returns the number of resolved classes that were profiled.
+  uint32_t GetNumberOfResolvedClasses() const;
 
   // Returns true if the method reference is present in the profiling info.
   bool ContainsMethod(const MethodReference& method_ref) const;
@@ -71,8 +80,8 @@
   std::string DumpInfo(const std::vector<const DexFile*>* dex_files,
                        bool print_full_dex_location = true) const;
 
-  // For testing purposes.
   bool Equals(const ProfileCompilationInfo& other);
+
   static std::string GetProfileDexFileKey(const std::string& dex_location);
 
   // Returns the class descriptors for all of the classes in the profiles' class sets.
@@ -80,6 +89,9 @@
   // profile info stuff to generate a map back to the dex location.
   std::set<DexCacheResolvedClasses> GetResolvedClasses() const;
 
+  // Clears the resolved classes from the current object.
+  void ClearResolvedClasses();
+
  private:
   struct DexFileData {
     explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {}
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index fdd8c6e..54fd69f 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -49,7 +49,7 @@
     return methods;
   }
 
-  bool AddData(const std::string& dex_location,
+  bool AddMethod(const std::string& dex_location,
                uint32_t checksum,
                uint16_t method_index,
                ProfileCompilationInfo* info) {
@@ -118,8 +118,8 @@
   ProfileCompilationInfo saved_info;
   // Save a few methods.
   for (uint16_t i = 0; i < 10; i++) {
-    ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
-    ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+    ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
   }
   ASSERT_TRUE(saved_info.Save(GetFd(profile)));
   ASSERT_EQ(0, profile.GetFile()->Flush());
@@ -132,9 +132,9 @@
 
   // Save more methods.
   for (uint16_t i = 0; i < 100; i++) {
-    ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
-    ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
-    ASSERT_TRUE(AddData("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info));
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+    ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
+    ASSERT_TRUE(AddMethod("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info));
   }
   ASSERT_TRUE(profile.GetFile()->ResetOffset());
   ASSERT_TRUE(saved_info.Save(GetFd(profile)));
@@ -147,25 +147,25 @@
   ASSERT_TRUE(loaded_info2.Equals(saved_info));
 }
 
-TEST_F(ProfileCompilationInfoTest, AddDataFail) {
+TEST_F(ProfileCompilationInfoTest, AddMethodsAndClassesFail) {
   ScratchFile profile;
 
   ProfileCompilationInfo info;
-  ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info));
+  ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info));
   // Trying to add info for an existing file but with a different checksum.
-  ASSERT_FALSE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info));
+  ASSERT_FALSE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info));
 }
 
-TEST_F(ProfileCompilationInfoTest, LoadFail) {
+TEST_F(ProfileCompilationInfoTest, MergeFail) {
   ScratchFile profile;
 
   ProfileCompilationInfo info1;
-  ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1));
+  ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1));
   // Use the same file, change the checksum.
   ProfileCompilationInfo info2;
-  ASSERT_TRUE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2));
+  ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2));
 
-  ASSERT_FALSE(info1.Load(info2));
+  ASSERT_FALSE(info1.MergeWith(info2));
 }
 
 }  // namespace art
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 3d3f3dd..81d81a5 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -29,18 +29,18 @@
 
 // An arbitrary value to throttle save requests. Set to 2s for now.
 static constexpr const uint64_t kMilisecondsToNano = 1000000;
-static constexpr const uint64_t kMinimumTimeBetweenCodeCacheUpdatesNs = 2000 * kMilisecondsToNano;
 
 // TODO: read the constants from ProfileOptions,
 // Add a random delay each time we go to sleep so that we don't hammer the CPU
 // with all profile savers running at the same time.
-static constexpr const uint64_t kRandomDelayMaxMs = 40 * 1000;  // 40 seconds
+static constexpr const uint64_t kRandomDelayMaxMs = 30 * 1000;  // 30 seconds
 static constexpr const uint64_t kMaxBackoffMs = 5 * 60 * 1000;  // 5 minutes
-static constexpr const uint64_t kSavePeriodMs = 40 * 1000;  // 40 seconds
+static constexpr const uint64_t kSavePeriodMs = 20 * 1000;  // 20 seconds
 static constexpr const uint64_t kSaveResolvedClassesDelayMs = 2 * 1000;  // 2 seconds
 static constexpr const double kBackoffCoef = 1.5;
 
-static constexpr const uint32_t kMinimumNrOrMethodsToSave = 10;
+static constexpr const uint32_t kMinimumNumberOfMethodsToSave = 10;
+static constexpr const uint32_t kMinimumNumberOfClassesToSave = 10;
 
 ProfileSaver* ProfileSaver::instance_ = nullptr;
 pthread_t ProfileSaver::profiler_pthread_ = 0U;
@@ -52,8 +52,9 @@
                            const std::string& app_data_dir)
     : jit_code_cache_(jit_code_cache),
       foreign_dex_profile_path_(foreign_dex_profile_path),
-      code_cache_last_update_time_ns_(0),
       shutting_down_(false),
+      last_save_number_of_methods_(0),
+      last_save_number_of_classes__(0),
       wait_lock_("ProfileSaver wait lock"),
       period_condition_("ProfileSaver period condition", wait_lock_),
       total_bytes_written_(0),
@@ -61,17 +62,14 @@
       total_number_of_code_cache_queries_(0),
       total_number_of_skipped_writes_(0),
       total_number_of_failed_writes_(0),
-      total_ns_of_sleep_(0),
+      total_ms_of_sleep_(0),
       total_ns_of_work_(0),
-      total_number_of_foreign_dex_marks_(0) {
+      total_number_of_foreign_dex_marks_(0),
+      max_number_of_profile_entries_cached_(0) {
   AddTrackedLocations(output_filename, code_paths);
   // We only need to save the resolved classes if the profile file is empty.
   // Otherwise we must have already save them (we always do it during the first
   // ever profile save).
-  // TODO(calin) This only considers the case of the primary profile file.
-  // Anything that gets loaded in the same VM will not have their resolved
-  // classes save (unless they started before the initial saving was done).
-  save_resolved_classes_ = !FileExistsAndNotEmpty(output_filename);
   app_data_dir_ = "";
   if (!app_data_dir.empty()) {
     // The application directory is used to determine which dex files are owned by app.
@@ -94,9 +92,10 @@
 
   uint64_t save_period_ms = kSavePeriodMs;
   VLOG(profiler) << "Save profiling information every " << save_period_ms << " ms";
+  bool cache_resolved_classes = true;
   while (!ShuttingDown(self)) {
     uint64_t sleep_time_ms;
-    if (save_resolved_classes_) {
+    if (cache_resolved_classes) {
       // Sleep less long for the first iteration since we want to record loaded classes shortly
       // after app launch.
       sleep_time_ms = kSaveResolvedClassesDelayMs;
@@ -108,41 +107,72 @@
       MutexLock mu(self, wait_lock_);
       period_condition_.TimedWait(self, sleep_time_ms, 0);
     }
-    total_ns_of_sleep_ += sleep_time_ms;
+    total_ms_of_sleep_ += sleep_time_ms;
     if (ShuttingDown(self)) {
       break;
     }
 
     uint64_t start = NanoTime();
-
-    if (!ProcessProfilingInfo(save_resolved_classes_) && save_period_ms < kMaxBackoffMs) {
-      // If we don't need to save now it is less likely that we will need to do
-      // so in the future. Increase the time between saves according to the
-      // kBackoffCoef, but make it no larger than kMaxBackoffMs.
-      save_period_ms = static_cast<uint64_t>(kBackoffCoef * save_period_ms);
+    if (cache_resolved_classes) {
+      // TODO(calin) This only considers the case of the primary profile file.
+      // Anything that gets loaded in the same VM will not have their resolved
+      // classes save (unless they started before the initial saving was done).
+      FetchAndCacheResolvedClasses();
     } else {
-      // Reset the period to the initial value as it's highly likely to JIT again.
-      save_period_ms = kSavePeriodMs;
+      bool profile_saved_to_disk = ProcessProfilingInfo();
+      if (!profile_saved_to_disk && save_period_ms < kMaxBackoffMs) {
+        // If we don't need to save now it is less likely that we will need to do
+        // so in the future. Increase the time between saves according to the
+        // kBackoffCoef, but make it no larger than kMaxBackoffMs.
+        save_period_ms = static_cast<uint64_t>(kBackoffCoef * save_period_ms);
+      } else {
+        // Reset the period to the initial value as it's highly likely to JIT again.
+        save_period_ms = kSavePeriodMs;
+      }
     }
-    save_resolved_classes_ = false;
+    cache_resolved_classes = false;
 
     total_ns_of_work_ += (NanoTime() - start);
   }
 }
 
-bool ProfileSaver::ProcessProfilingInfo(bool save_resolved_classes) {
-  ScopedTrace trace(__PRETTY_FUNCTION__);
-  uint64_t last_update_time_ns = jit_code_cache_->GetLastUpdateTimeNs();
-  if (!save_resolved_classes && last_update_time_ns - code_cache_last_update_time_ns_
-          < kMinimumTimeBetweenCodeCacheUpdatesNs) {
-    VLOG(profiler) << "Not enough time has passed since the last code cache update."
-        << "Last update: " << last_update_time_ns
-        << " Last save: " << code_cache_last_update_time_ns_;
-    total_number_of_skipped_writes_++;
-    return false;
+ProfileCompilationInfo* ProfileSaver::GetCachedProfiledInfo(const std::string& filename) {
+  auto info_it = profile_cache_.find(filename);
+  if (info_it == profile_cache_.end()) {
+    info_it = profile_cache_.Put(filename, ProfileCompilationInfo());
   }
+  return &info_it->second;
+}
 
-  code_cache_last_update_time_ns_ = last_update_time_ns;
+void ProfileSaver::FetchAndCacheResolvedClasses() {
+  ScopedTrace trace(__PRETTY_FUNCTION__);
+
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  std::set<DexCacheResolvedClasses> resolved_classes =
+      class_linker->GetResolvedClasses(/*ignore boot classes*/ true);
+  MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
+  uint64_t total_number_of_profile_entries_cached = 0;
+  for (const auto& it : tracked_dex_base_locations_) {
+      std::set<DexCacheResolvedClasses> resolved_classes_for_location;
+    const std::string& filename = it.first;
+    const std::set<std::string>& locations = it.second;
+
+    for (const DexCacheResolvedClasses& classes : resolved_classes) {
+      if (locations.find(classes.GetDexLocation()) != locations.end()) {
+        resolved_classes_for_location.insert(classes);
+      }
+    }
+    ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
+    info->AddMethodsAndClasses(std::vector<ArtMethod*>(), resolved_classes_for_location);
+    total_number_of_profile_entries_cached += resolved_classes_for_location.size();
+  }
+  max_number_of_profile_entries_cached_ = std::max(
+      max_number_of_profile_entries_cached_,
+      total_number_of_profile_entries_cached);
+}
+
+bool ProfileSaver::ProcessProfilingInfo() {
+  ScopedTrace trace(__PRETTY_FUNCTION__);
   SafeMap<std::string, std::set<std::string>> tracked_locations;
   {
     // Make a copy so that we don't hold the lock while doing I/O.
@@ -150,11 +180,8 @@
     tracked_locations = tracked_dex_base_locations_;
   }
 
-  std::set<DexCacheResolvedClasses> resolved_classes;
-  if (save_resolved_classes) {
-    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
-    resolved_classes = class_linker->GetResolvedClasses(/*ignore boot classes*/true);
-  }
+  bool profile_file_saved = false;
+  uint64_t total_number_of_profile_entries_cached = 0;
   for (const auto& it : tracked_locations) {
     if (ShuttingDown(Thread::Current())) {
       return true;
@@ -168,41 +195,53 @@
       total_number_of_code_cache_queries_++;
     }
 
-    std::set<DexCacheResolvedClasses> resolved_classes_for_location;
-    if (save_resolved_classes) {
-      bool resolved_classes_already_in_file = FileExistsAndNotEmpty(filename);
-      if (!resolved_classes_already_in_file) {
-        for (const DexCacheResolvedClasses& classes : resolved_classes) {
-          if (locations.find(classes.GetDexLocation()) != locations.end()) {
-            resolved_classes_for_location.insert(classes);
-          }
-        }
-      }
-    }
-    // Always save for the first one for loaded classes profile.
-    if (methods.size() < kMinimumNrOrMethodsToSave && !save_resolved_classes) {
-      VLOG(profiler) << "Not enough information to save to: " << filename
-          <<" Nr of methods: " << methods.size();
+    ProfileCompilationInfo* cached_info = GetCachedProfiledInfo(filename);
+    cached_info->AddMethodsAndClasses(methods, std::set<DexCacheResolvedClasses>());
+    uint32_t delta_number_of_methods =
+        cached_info->GetNumberOfMethods() - last_save_number_of_methods_;
+    uint32_t delta_number_of_classes =
+        cached_info->GetNumberOfResolvedClasses() - last_save_number_of_classes__;
+
+    if (delta_number_of_methods < kMinimumNumberOfMethodsToSave &&
+        delta_number_of_classes < kMinimumNumberOfClassesToSave) {
+     VLOG(profiler) << "Not enough information to save to: " << filename
+          << " Nr of methods: " << delta_number_of_methods
+          << " Nr of classes: " << delta_number_of_classes;
       total_number_of_skipped_writes_++;
-      return false;
+      continue;
     }
     uint64_t bytes_written;
-    if (!ProfileCompilationInfo::SaveProfilingInfo(
-            filename,
-            methods,
-            resolved_classes_for_location,
-            &bytes_written)) {
-      LOG(WARNING) << "Could not save profiling info to " << filename;
-      total_number_of_failed_writes_++;
-      return false;
-    } else {
+    if (cached_info->MergeAndSave(filename, &bytes_written)) {
+      last_save_number_of_methods_ = cached_info->GetNumberOfMethods();
+      last_save_number_of_classes__ = cached_info->GetNumberOfResolvedClasses();
+      // Clear resolved classes. No need to store them around as
+      // they don't change after the first write.
+      cached_info->ClearResolvedClasses();
       if (bytes_written > 0) {
         total_number_of_writes_++;
         total_bytes_written_ += bytes_written;
+      } else {
+        // At this point we could still have avoided the write.
+        // We load and merge the data from the file lazily at its first ever
+        // save attempt. So, whatever we are trying to save could already be
+        // in the file.
+        total_number_of_skipped_writes_++;
       }
+      profile_file_saved = true;
+    } else {
+      LOG(WARNING) << "Could not save profiling info to " << filename;
+      total_number_of_failed_writes_++;
+      // TODO: (calin): if we failed because of bad profiling data or parsing
+      // errors we should clear the profile file.
     }
+    total_number_of_profile_entries_cached +=
+        cached_info->GetNumberOfMethods() +
+        cached_info->GetNumberOfResolvedClasses();
   }
-  return true;
+  max_number_of_profile_entries_cached_ = std::max(
+      max_number_of_profile_entries_cached_,
+      total_number_of_profile_entries_cached);
+  return profile_file_saved;
 }
 
 void* ProfileSaver::RunProfileSaverThread(void* arg) {
@@ -431,12 +470,16 @@
 void ProfileSaver::DumpInfo(std::ostream& os) {
   os << "ProfileSaver total_bytes_written=" << total_bytes_written_ << '\n'
      << "ProfileSaver total_number_of_writes=" << total_number_of_writes_ << '\n'
-     << "ProfileSaver total_number_of_code_cache_queries=" << total_number_of_code_cache_queries_ << '\n'
+     << "ProfileSaver total_number_of_code_cache_queries="
+     << total_number_of_code_cache_queries_ << '\n'
      << "ProfileSaver total_number_of_skipped_writes=" << total_number_of_skipped_writes_ << '\n'
      << "ProfileSaver total_number_of_failed_writes=" << total_number_of_failed_writes_ << '\n'
-     << "ProfileSaver total_ms_of_sleep=" << (total_ns_of_sleep_ / kMilisecondsToNano) << '\n'
+     << "ProfileSaver total_ms_of_sleep=" << total_ms_of_sleep_ << '\n'
      << "ProfileSaver total_ms_of_work=" << (total_ns_of_work_ / kMilisecondsToNano) << '\n'
-     << "ProfileSaver total_number_of_foreign_dex_marks=" << total_number_of_foreign_dex_marks_ << '\n';
+     << "ProfileSaver total_number_of_foreign_dex_marks="
+     << total_number_of_foreign_dex_marks_ << '\n'
+     << "ProfileSaver max_number_profile_entries_cached="
+    << max_number_of_profile_entries_cached_ << '\n';
 }
 
 }   // namespace art
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index d810c81..91390fe 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -65,7 +65,7 @@
   void Run() REQUIRES(!Locks::profiler_lock_, !wait_lock_);
   // Processes the existing profiling info from the jit code cache and returns
   // true if it needed to be saved to disk.
-  bool ProcessProfilingInfo(bool save_resolved_classes);
+  bool ProcessProfilingInfo();
   // Returns true if the saver is shutting down (ProfileSaver::Stop() has been called).
   bool ShuttingDown(Thread* self) REQUIRES(!Locks::profiler_lock_);
 
@@ -73,6 +73,14 @@
                            const std::vector<std::string>& code_paths)
       REQUIRES(Locks::profiler_lock_);
 
+  // Retrieves the cached profile compilation info for the given profile file.
+  // If no entry exists, a new empty one will be created, added to the cache and
+  // then returned.
+  ProfileCompilationInfo* GetCachedProfiledInfo(const std::string& filename);
+  // Fetches the current resolved classes from the ClassLinker and stores them
+  // in the profile_cache_ for later save.
+  void FetchAndCacheResolvedClasses();
+
   static bool MaybeRecordDexUseInternal(
       const std::string& dex_location,
       const std::set<std::string>& tracked_locations,
@@ -91,9 +99,15 @@
       GUARDED_BY(Locks::profiler_lock_);
   std::string foreign_dex_profile_path_;
   std::string app_data_dir_;
-  uint64_t code_cache_last_update_time_ns_;
   bool shutting_down_ GUARDED_BY(Locks::profiler_lock_);
-  bool save_resolved_classes_;
+  uint32_t last_save_number_of_methods_;
+  uint32_t last_save_number_of_classes__;
+
+  // A local cache for the profile information. Maps each tracked file to its
+  // profile information. The size of this cache is usually very small and tops
+  // to just a few hundreds entries in the ProfileCompilationInfo objects.
+  // It helps avoiding unnecessary writes to disk.
+  SafeMap<std::string, ProfileCompilationInfo> profile_cache_;
 
   // Save period condition support.
   Mutex wait_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
@@ -104,9 +118,11 @@
   uint64_t total_number_of_code_cache_queries_;
   uint64_t total_number_of_skipped_writes_;
   uint64_t total_number_of_failed_writes_;
-  uint64_t total_ns_of_sleep_;
+  uint64_t total_ms_of_sleep_;
   uint64_t total_ns_of_work_;
   uint64_t total_number_of_foreign_dex_marks_;
+  // TODO(calin): replace with an actual size.
+  uint64_t max_number_of_profile_entries_cached_;
 
   DISALLOW_COPY_AND_ASSIGN(ProfileSaver);
 };