]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-112320: Implement on-trace confidence tracking for branches (#112321)
authorGuido van Rossum <guido@python.org>
Tue, 12 Dec 2023 21:43:08 +0000 (13:43 -0800)
committerGitHub <noreply@github.com>
Tue, 12 Dec 2023 21:43:08 +0000 (21:43 +0000)
We track the confidence as a scaled int.

Include/cpython/pystats.h
Lib/test/test_capi/test_misc.py
Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst [new file with mode: 0644]
Python/optimizer.c
Python/specialize.c
Tools/scripts/summarize_stats.py

index 294bf1505f01153a60a4c75bd1868f3842735b6d..ba67eefef3e37ad7b1f9060211287505a16d212d 100644 (file)
@@ -114,6 +114,7 @@ typedef struct _optimization_stats {
     uint64_t trace_too_short;
     uint64_t inner_loop;
     uint64_t recursive_call;
+    uint64_t low_confidence;
     UOpStats opcode[512];
     uint64_t unsupported_opcode[256];
     uint64_t trace_length_hist[_Py_UOP_HIST_SIZE];
index e6b532e858c8f9bec0f658b4115dd2b2dbc50568..776ee913a022161dabf75253e44fc9b2e7a672a5 100644 (file)
@@ -2985,6 +2985,37 @@ class TestUops(unittest.TestCase):
         uops = {opname for opname, _, _ in ex}
         self.assertIn("_FOR_ITER_TIER_TWO", uops)
 
+    def test_confidence_score(self):
+        def testfunc(n):
+            bits = 0
+            for i in range(n):
+                if i & 0x01:
+                    bits += 1
+                if i & 0x02:
+                    bits += 1
+                if i&0x04:
+                    bits += 1
+                if i&0x08:
+                    bits += 1
+                if i&0x10:
+                    bits += 1
+                if i&0x20:
+                    bits += 1
+            return bits
+
+        opt = _testinternalcapi.get_uop_optimizer()
+        with temporary_optimizer(opt):
+            x = testfunc(20)
+
+        self.assertEqual(x, 40)
+        ex = get_first_executor(testfunc)
+        self.assertIsNotNone(ex)
+        ops = [opname for opname, _, _ in ex]
+        count = ops.count("_GUARD_IS_TRUE_POP")
+        # Because Each 'if' halves the score, the second branch is
+        # too much already.
+        self.assertEqual(count, 1)
+
 
 @unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED')
 class TestPyThreadId(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst
new file mode 100644 (file)
index 0000000..0da2fd3
--- /dev/null
@@ -0,0 +1,4 @@
+The Tier 2 translator now tracks the confidence level for staying "on trace"
+(i.e. not exiting back to the Tier 1 interpreter) for branch instructions
+based on the number of bits set in the branch "counter". Trace translation
+ends when the confidence drops below 1/3rd.
index 7c46bd69157170730a80a22da5f40242bdfa3cf4..d44e733bc346fa90292c27b293ef40fa4281fc33 100644 (file)
@@ -409,6 +409,9 @@ BRANCH_TO_GUARD[4][2] = {
 
 #define TRACE_STACK_SIZE 5
 
+#define CONFIDENCE_RANGE 1000
+#define CONFIDENCE_CUTOFF 333
+
 /* Returns 1 on success,
  * 0 if it failed to produce a worthwhile trace,
  * and -1 on an error.
@@ -431,6 +434,7 @@ translate_bytecode_to_trace(
         _Py_CODEUNIT *instr;
     } trace_stack[TRACE_STACK_SIZE];
     int trace_stack_depth = 0;
+    int confidence = CONFIDENCE_RANGE;  // Adjusted by branch instructions
 
 #ifdef Py_DEBUG
     char *python_lltrace = Py_GETENV("PYTHON_LLTRACE");
@@ -513,7 +517,6 @@ top:  // Jump here after _PUSH_FRAME or likely branches
         uint32_t oparg = instr->op.arg;
         uint32_t extras = 0;
 
-
         if (opcode == EXTENDED_ARG) {
             instr++;
             extras += 1;
@@ -543,11 +546,22 @@ top:  // Jump here after _PUSH_FRAME or likely branches
                 int counter = instr[1].cache;
                 int bitcount = _Py_popcount32(counter);
                 int jump_likely = bitcount > 8;
+                if (jump_likely) {
+                    confidence = confidence * bitcount / 16;
+                }
+                else {
+                    confidence = confidence * (16 - bitcount) / 16;
+                }
+                if (confidence < CONFIDENCE_CUTOFF) {
+                    DPRINTF(2, "Confidence too low (%d)\n", confidence);
+                    OPT_STAT_INC(low_confidence);
+                    goto done;
+                }
                 uint32_t uopcode = BRANCH_TO_GUARD[opcode - POP_JUMP_IF_FALSE][jump_likely];
                 _Py_CODEUNIT *next_instr = instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]];
-                DPRINTF(4, "%s(%d): counter=%x, bitcount=%d, likely=%d, uopcode=%s\n",
+                DPRINTF(2, "%s(%d): counter=%x, bitcount=%d, likely=%d, confidence=%d, uopcode=%s\n",
                         _PyUOpName(opcode), oparg,
-                        counter, bitcount, jump_likely, _PyUOpName(uopcode));
+                        counter, bitcount, jump_likely, confidence, _PyUOpName(uopcode));
                 ADD_TO_TRACE(uopcode, max_length, 0, target);
                 if (jump_likely) {
                     _Py_CODEUNIT *target_instr = next_instr + oparg;
index ba704cbbb464d71570868456eeb0fe49e3834b59..7c2a4a42b1dcc3dca0fa8ded667a38c359cf56b7 100644 (file)
@@ -233,6 +233,7 @@ print_optimization_stats(FILE *out, OptimizationStats *stats)
     fprintf(out, "Optimization trace too short: %" PRIu64 "\n", stats->trace_too_short);
     fprintf(out, "Optimization inner loop: %" PRIu64 "\n", stats->inner_loop);
     fprintf(out, "Optimization recursive call: %" PRIu64 "\n", stats->recursive_call);
+    fprintf(out, "Optimization low confidence: %" PRIu64 "\n", stats->low_confidence);
 
     print_histogram(out, "Trace length", stats->trace_length_hist);
     print_histogram(out, "Trace run length", stats->trace_run_length_hist);
index 360b7c720bd1f094f5048af1cdfd0ff0a0184c85..80a1280c025acaedea726273f7f5fc878b941f77 100644 (file)
@@ -386,6 +386,7 @@ class Stats:
         trace_too_short = self._data["Optimization trace too short"]
         inner_loop = self._data["Optimization inner loop"]
         recursive_call = self._data["Optimization recursive call"]
+        low_confidence = self._data["Optimization low confidence"]
 
         return {
             "Optimization attempts": (attempts, None),
@@ -396,6 +397,7 @@ class Stats:
             "Trace too short": (trace_too_short, attempts),
             "Inner loop found": (inner_loop, attempts),
             "Recursive call": (recursive_call, attempts),
+            "Low confidence": (low_confidence, attempts),
             "Traces executed": (executed, None),
             "Uops executed": (uops, executed),
         }