[scudo] Improve performance of pushBlocks sort. (#200297)

Ran this on an Android device using both algorithms, the new algorithm
is on average 10% faster, but gets to be 15% faster in some cases. This
is an example of the speed-ups.

Average Operation Time    Maximum Operation Time   Name
        326.9(ns)                 80770(ns)        PushBlocks New
        365.9(ns)                108032(ns)        PushBlocks Old

GitOrigin-RevId: c9cdbc4a30b83b8a8d100c19c4aba16c848b9dfb
diff --git a/primary64.h b/primary64.h
index 2c3ccdf..b8ff156 100644
--- a/primary64.h
+++ b/primary64.h
@@ -802,21 +802,24 @@
 
   // TODO(chiahungduan): Consider not doing grouping if the group size is not
   // greater than the block size with a certain scale.
-
   bool SameGroup = true;
-  if (GroupSizeLog < RegionSizeLog) {
-    // Sort the blocks so that blocks belonging to the same group can be
-    // pushed together.
+  if (GroupSizeLog < RegionSizeLog && Size > 1) {
+    // Sort the blocks such that blocks belonging to the same group are
+    // ordered together.
+    uptr FirstPtrGroup = compactPtrGroup(Array[0]);
     for (u32 I = 1; I < Size; ++I) {
-      if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I]))
-        SameGroup = false;
       CompactPtrT Cur = Array[I];
-      u32 J = I;
-      while (J > 0 && compactPtrGroup(Cur) < compactPtrGroup(Array[J - 1])) {
-        Array[J] = Array[J - 1];
-        --J;
+      uptr CurPtrGroup = compactPtrGroup(Cur);
+      SameGroup = SameGroup && CurPtrGroup == FirstPtrGroup;
+      if (!SameGroup) {
+        // Sorting only necessary if there are different groups.
+        u32 J = I;
+        while (J > 0 && CurPtrGroup < compactPtrGroup(Array[J - 1])) {
+          Array[J] = Array[J - 1];
+          --J;
+        }
+        Array[J] = Cur;
       }
-      Array[J] = Cur;
     }
   }
 
diff --git a/tests/primary_test.cpp b/tests/primary_test.cpp
index 2fbfadf..cd4b0b8 100644
--- a/tests/primary_test.cpp
+++ b/tests/primary_test.cpp
@@ -503,3 +503,68 @@
   }
   SizeClassAllocator.drain();
 }
+
+#if !defined(GWP_ASAN_HOOKS)
+// This test doesn't work when GWP-Asan is enabled.
+TEST(ScudoPrimaryTest, PushBlocksDifferentGroups) {
+  using Primary = TestAllocator<TestConfig2, scudo::DefaultSizeClassMap>;
+  std::unique_ptr<Primary> Allocator(new Primary);
+  Allocator->init(/*ReleaseToOsInterval=*/-1);
+  typename Primary::SizeClassAllocatorT SizeClassAllocator;
+  SizeClassAllocator.init(nullptr, Allocator.get());
+
+  static_assert(
+      TestConfig2<scudo::DefaultSizeClassMap>::Primary::GroupSizeLog <
+      TestConfig2<scudo::DefaultSizeClassMap>::Primary::RegionSizeLog);
+
+  const scudo::uptr GroupScale =
+      TestConfig2<scudo::DefaultSizeClassMap>::Primary::GroupSizeLog -
+      TestConfig2<scudo::DefaultSizeClassMap>::Primary::CompactPtrScale;
+  const scudo::uptr Mask = (static_cast<scudo::uptr>(1) << GroupScale) - 1;
+
+  scudo::uptr ClassId = 1;
+  for (scudo::uptr C = 1; C < Primary::SizeClassMap::NumClasses; ++C) {
+    if (Primary::SizeClassMap::getSizeByClassId(C) >= 64 * 1024) {
+      ClassId = C;
+      break;
+    }
+  }
+  EXPECT_NE(1U, ClassId);
+
+  std::vector<void *> Pointers;
+  size_t SecondGroupIndex = 0;
+  for (scudo::uptr FirstGroup = 0, I = 0; I < 100; ++I) {
+    void *P = SizeClassAllocator.allocate(ClassId);
+    EXPECT_NE(P, nullptr);
+    Pointers.push_back(P);
+    typename Primary::CompactPtrT CompactP =
+        Allocator->compactPtr(ClassId, reinterpret_cast<scudo::uptr>(P));
+    scudo::uptr Group = static_cast<scudo::uptr>(CompactP) & ~Mask;
+    if (I == 0) {
+      FirstGroup = Group;
+    } else if (Group != FirstGroup) {
+      SecondGroupIndex = I;
+      break;
+    }
+  }
+  EXPECT_NE(0U, SecondGroupIndex);
+
+  std::vector<typename Primary::CompactPtrT> Array;
+  for (void *P : Pointers) {
+    Array.push_back(
+        Allocator->compactPtr(ClassId, reinterpret_cast<scudo::uptr>(P)));
+  }
+  EXPECT_GT(Array.size(), 1U);
+
+  std::swap(Array[0], Array[SecondGroupIndex]);
+
+  Allocator->pushBlocks(&SizeClassAllocator, ClassId, Array.data(),
+                        static_cast<scudo::u32>(Array.size()));
+
+  for (size_t I = 0; I < Array.size(); I++)
+    EXPECT_LE(Array[I - 1] & ~Mask, Array[I] & ~Mask)
+        << "Array not ordered: index " << I - 1 << " is < index " << I;
+
+  SizeClassAllocator.destroy(nullptr);
+}
+#endif