Combine madvise for clearing region space

Reduce how many madvise calls there are so that the mmap semaphore
has less contention.

Reduces the number of madvice calls from clearing regions when doing
few random camera gestures by ~80%.

Test: test-art-host
Bug: 62194020

Change-Id: I49987a371c5724e6993cfc74e385a03b624352c9
diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc
index 09b4a3a..e792531 100644
--- a/runtime/gc/space/region_space.cc
+++ b/runtime/gc/space/region_space.cc
@@ -254,13 +254,28 @@
   MutexLock mu(Thread::Current(), region_lock_);
   VerifyNonFreeRegionLimit();
   size_t new_non_free_region_index_limit = 0;
+
+  // Combine zeroing and releasing pages to reduce how often madvise is called. This helps
+  // reduce contention on the mmap semaphore. b/62194020
+  // clear_region adds a region to the current block. If the region is not adjacent, the
+  // clear block is zeroed, released, and a new block begins.
+  uint8_t* clear_block_begin = nullptr;
+  uint8_t* clear_block_end = nullptr;
+  auto clear_region = [&clear_block_begin, &clear_block_end](Region* r) {
+    r->Clear(/*zero_and_release_pages*/false);
+    if (clear_block_end != r->Begin()) {
+      ZeroAndReleasePages(clear_block_begin, clear_block_end - clear_block_begin);
+      clear_block_begin = r->Begin();
+    }
+    clear_block_end = r->End();
+  };
   for (size_t i = 0; i < std::min(num_regions_, non_free_region_index_limit_); ++i) {
     Region* r = &regions_[i];
     if (r->IsInFromSpace()) {
       *cleared_bytes += r->BytesAllocated();
       *cleared_objects += r->ObjectsAllocated();
       --num_non_free_regions_;
-      r->Clear();
+      clear_region(r);
     } else if (r->IsInUnevacFromSpace()) {
       if (r->LiveBytes() == 0) {
         // Special case for 0 live bytes, this means all of the objects in the region are dead and
@@ -273,13 +288,13 @@
         // Also release RAM for large tails.
         while (i + free_regions < num_regions_ && regions_[i + free_regions].IsLargeTail()) {
           DCHECK(r->IsLarge());
-          regions_[i + free_regions].Clear();
+          clear_region(&regions_[i + free_regions]);
           ++free_regions;
         }
         *cleared_bytes += r->BytesAllocated();
         *cleared_objects += r->ObjectsAllocated();
         num_non_free_regions_ -= free_regions;
-        r->Clear();
+        clear_region(r);
         GetLiveBitmap()->ClearRange(
             reinterpret_cast<mirror::Object*>(r->Begin()),
             reinterpret_cast<mirror::Object*>(r->Begin() + free_regions * kRegionSize));
@@ -316,6 +331,8 @@
                                                  last_checked_region->Idx() + 1);
     }
   }
+  // Clear pages for the last block since clearing happens when a new block opens.
+  ZeroAndReleasePages(clear_block_begin, clear_block_end - clear_block_begin);
   // Update non_free_region_index_limit_.
   SetNonFreeRegionLimit(new_non_free_region_index_limit);
   evac_region_ = nullptr;
@@ -368,7 +385,7 @@
     if (!r->IsFree()) {
       --num_non_free_regions_;
     }
-    r->Clear();
+    r->Clear(/*zero_and_release_pages*/true);
   }
   SetNonFreeRegionLimit(0);
   current_region_ = &full_region_;
@@ -394,7 +411,7 @@
     } else {
       DCHECK(reg->IsLargeTail());
     }
-    reg->Clear();
+    reg->Clear(/*zero_and_release_pages*/true);
     --num_non_free_regions_;
   }
   if (end_addr < Limit()) {
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index 80eecca..4dea0fa 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -280,14 +280,16 @@
       return type_;
     }
 
-    void Clear() {
+    void Clear(bool zero_and_release_pages) {
       top_.StoreRelaxed(begin_);
       state_ = RegionState::kRegionStateFree;
       type_ = RegionType::kRegionTypeNone;
       objects_allocated_.StoreRelaxed(0);
       alloc_time_ = 0;
       live_bytes_ = static_cast<size_t>(-1);
-      ZeroAndReleasePages(begin_, end_ - begin_);
+      if (zero_and_release_pages) {
+        ZeroAndReleasePages(begin_, end_ - begin_);
+      }
       is_newly_allocated_ = false;
       is_a_tlab_ = false;
       thread_ = nullptr;
diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc
index 6c39361..12793e4 100644
--- a/runtime/mem_map.cc
+++ b/runtime/mem_map.cc
@@ -952,6 +952,9 @@
 }
 
 void ZeroAndReleasePages(void* address, size_t length) {
+  if (length == 0) {
+    return;
+  }
   uint8_t* const mem_begin = reinterpret_cast<uint8_t*>(address);
   uint8_t* const mem_end = mem_begin + length;
   uint8_t* const page_begin = AlignUp(mem_begin, kPageSize);