This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch master
in repository expedite.

View the commit online.

commit 9b519029474e75d736bf840c0802651dce7af846
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 2 20:56:15 2026 -0600

    add --cluster to pin to the big or the little cores
    
    --cpu already pins, but it needs the caller to know which numbers are
    which, and that varies per board. --cluster=big and --cluster=little ask
    for a kind of core instead.
    
    Classification comes from cpu_capacity, which is what the kernel itself
    uses to tell asymmetric cores apart on arm64 - an RK3399 reports 381 for
    its Cortex-A53s against 1024 for its A72s. Where that file is absent the
    maximum frequency stands in: not the same quantity, but it orders the
    clusters the same way on every big.LITTLE part in practice. If every CPU
    reports the same value there is nothing to choose between, and that is
    said rather than silently ignored.
    
    It pins to every core of the chosen kind rather than to one of them. The
    microarchitecture is what has to be fixed for results to be comparable;
    leaving the scheduler more than one core to use costs nothing and helps
    when something else wants the machine.
    
        $ expedite -e buffer -t 74 -m --cluster=big
        Pinned to 2 big cores (capacity 1024, of 1024).
        4.53 , Textblock text_fill_format
    
        $ expedite -e buffer -t 74 -m --cluster=little
        Pinned to 4 little cores (capacity 381, of 1024).
        2.67 , Textblock text_fill_format
    
    That 1.7x is the whole point: unpinned, the same test lands on either
    cluster from run to run, which is enough to invent or hide a difference
    between two builds entirely.
    
    Asking for both --cluster and --cpu is an error rather than a silent
    precedence rule. Linux only, as with --cpu.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 src/bin/main.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 121 insertions(+), 9 deletions(-)

diff --git a/src/bin/main.c b/src/bin/main.c
index d3a44f5..a111501 100644
--- a/src/bin/main.c
+++ b/src/bin/main.c
@@ -1102,6 +1102,90 @@ build_path(const char *filename)
    return buffer;
 }
 
+
+#ifdef __linux__
+/* Classify CPUs by performance domain, so a benchmark can be kept on one kind
+ * of core without the caller having to know the numbering.
+ *
+ * cpu_capacity is what the kernel itself uses to tell asymmetric cores apart
+ * on arm64, and is the right answer where it exists. Where it does not,
+ * maximum frequency is a reasonable proxy: it is not the same quantity, but it
+ * orders the clusters the same way on every big.LITTLE part in practice.
+ */
+static long
+_cpu_metric(int cpu)
+{
+   static const char *files[] = {
+      "/sys/devices/system/cpu/cpu%d/cpu_capacity",
+      "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq",
+      NULL
+   };
+   char path[PATH_MAX];
+   unsigned int i;
+
+   for (i = 0; files[i]; i++)
+     {
+        FILE *f;
+        long v = -1;
+
+        snprintf(path, sizeof(path), files[i], cpu);
+        if (!(f = fopen(path, "r"))) continue;
+        if (fscanf(f, "%ld", &v) != 1) v = -1;
+        fclose(f);
+        if (v > 0) return v;
+     }
+   return -1;
+}
+
+/* Pin to every core of the fastest (or slowest) kind. Pinning to the cluster
+ * rather than to one core still guarantees the microarchitecture, and leaves
+ * the scheduler somewhere to go if something else wants the machine. */
+static Eina_Bool
+_pin_to_cluster(Eina_Bool big)
+{
+   const int n = (int) sysconf(_SC_NPROCESSORS_CONF);
+   long fastest = -1, slowest = -1, want;
+   int i, count = 0;
+   cpu_set_t set;
+
+   for (i = 0; i < n; i++)
+     {
+        long m = _cpu_metric(i);
+
+        if (m < 0) continue;
+        if ((fastest < 0) || (m > fastest)) fastest = m;
+        if ((slowest < 0) || (m < slowest)) slowest = m;
+     }
+   if (fastest < 0)
+     {
+        fprintf(stderr, "Could not read CPU capacities; use --cpu=N instead.\n");
+        return EINA_FALSE;
+     }
+   if (fastest == slowest)
+     {
+        fprintf(stderr, "All CPUs are of one kind, there is no big or little "
+                        "here; run without --cluster.\n");
+        return EINA_FALSE;
+     }
+
+   want = big ? fastest : slowest;
+   CPU_ZERO(&set);
+   for (i = 0; i < n; i++)
+     if (_cpu_metric(i) == want) { CPU_SET(i, &set); count++; }
+
+   if (sched_setaffinity(0, sizeof(set), &set) != 0)
+     {
+        fprintf(stderr, "Could not pin to the %s cores.\n",
+                big ? "big" : "little");
+        return EINA_FALSE;
+     }
+   fprintf(stderr, "Pinned to %i %s core%s (capacity %ld, of %ld).\n",
+           count, big ? "big" : "little", (count > 1) ? "s" : "",
+           want, fastest);
+   return EINA_TRUE;
+}
+#endif
+
 static const Ecore_Getopt optdesc = {
   "expedite",
   "%prog [options] <filename.edj>",
@@ -1126,6 +1210,9 @@ static const Ecore_Getopt optdesc = {
     ECORE_GETOPT_STORE_TRUE('y', "async", "Enable async output"),
     ECORE_GETOPT_STORE_TRUE('a', "all", "Run all tests"),
     ECORE_GETOPT_STORE_FALSE('i', "tick", "Follow output animator tick"),
+    ECORE_GETOPT_STORE_STR('B', "cluster", "Pin the benchmark to the \"big\" or "
+                           "the \"little\" cores, whichever the machine calls "
+                           "them, without needing to know their numbers"),
     ECORE_GETOPT_STORE_INT('u', "cpu", "Pin the benchmark to one CPU, by number "
                            "(see /proc/cpuinfo). On big.LITTLE the two cluster "
                            "types differ enough that unpinned results are not "
@@ -1257,6 +1344,7 @@ main(int argc, char **argv)
    Eina_Bool all_tests = EINA_FALSE;
    Eina_Bool quit_option = EINA_FALSE;
    int cpu = -1;
+   char *cluster = NULL;
    Ecore_Getopt_Value values[] = {
      ECORE_GETOPT_VALUE_STR(engine),
      ECORE_GETOPT_VALUE_BOOL(quit_option),
@@ -1272,6 +1360,7 @@ main(int argc, char **argv)
      ECORE_GETOPT_VALUE_BOOL(async),
      ECORE_GETOPT_VALUE_BOOL(all_tests),
      ECORE_GETOPT_VALUE_BOOL(tick),
+     ECORE_GETOPT_VALUE_STR(cluster),
      ECORE_GETOPT_VALUE_INT(cpu),
      ECORE_GETOPT_VALUE_BOOL(quit_option),
      ECORE_GETOPT_VALUE_BOOL(quit_option),
@@ -1298,20 +1387,43 @@ main(int argc, char **argv)
         return 0;
      }
 
-   if (cpu >= 0)
+   if (cluster && (cpu >= 0))
+     {
+        fprintf(stderr, "--cluster and --cpu both pin; pick one.\n");
+        return -1;
+     }
+
+   if (cluster || (cpu >= 0))
      {
 #ifdef __linux__
-        cpu_set_t set;
-
-        CPU_ZERO(&set);
-        CPU_SET(cpu, &set);
-        if (sched_setaffinity(0, sizeof(set), &set) != 0)
+        if (cluster)
           {
-             fprintf(stderr, "Could not pin to CPU %i.\n", cpu);
-             return -1;
+             Eina_Bool big;
+
+             if (!strcasecmp(cluster, "big")) big = EINA_TRUE;
+             else if (!strcasecmp(cluster, "little")) big = EINA_FALSE;
+             else
+               {
+                  fprintf(stderr, "Unknown cluster '%s', expected big or "
+                                  "little.\n", cluster);
+                  return -1;
+               }
+             if (!_pin_to_cluster(big)) return -1;
+          }
+        else
+          {
+             cpu_set_t set;
+
+             CPU_ZERO(&set);
+             CPU_SET(cpu, &set);
+             if (sched_setaffinity(0, sizeof(set), &set) != 0)
+               {
+                  fprintf(stderr, "Could not pin to CPU %i.\n", cpu);
+                  return -1;
+               }
           }
 #else
-        fprintf(stderr, "--cpu is only implemented on Linux.\n");
+        fprintf(stderr, "--cluster and --cpu are only implemented on Linux.\n");
         return -1;
 #endif
      }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to