From d0816c7b512aad977d16cc8afc8e8b25f2806572 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:01:00 +0800 Subject: [PATCH 1/4] Disable the tier 2 redundancy eliminator by default --- Python/optimizer_analysis.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 9503dcc74656cd..108eb7259afdec 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -810,9 +810,12 @@ _Py_uop_analyze_and_optimize( peephole_opt(frame, buffer, buffer_size); - err = uop_redundancy_eliminator( - (PyCodeObject *)frame->f_executable, buffer, - buffer_size, curr_stacklen, dependencies); + char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); + if (*uop_optimize > '0') { + err = uop_redundancy_eliminator( + (PyCodeObject *)frame->f_executable, buffer, + buffer_size, curr_stacklen, dependencies); + } if (err == 0) { goto not_ready; From dca1ec825af4fb306152ed5e8cf695fabda7aa46 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:24:46 +0800 Subject: [PATCH 2/4] fix check --- Python/optimizer_analysis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 108eb7259afdec..47bfc8cf1061d9 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -811,7 +811,7 @@ _Py_uop_analyze_and_optimize( peephole_opt(frame, buffer, buffer_size); char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); - if (*uop_optimize > '0') { + if (uop_optimize != NULL && *uop_optimize > '0') { err = uop_redundancy_eliminator( (PyCodeObject *)frame->f_executable, buffer, buffer_size, curr_stacklen, dependencies); From 2306f3023400d322b0fb67b14db81e3a9351b027 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:33:44 +0800 Subject: [PATCH 3/4] remove uops optimization tests --- Lib/test/test_capi/test_opt.py | 318 --------------------------------- 1 file changed, 318 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 38c6fa4b47d0c9..3f1ba984cfbc19 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -568,324 +568,6 @@ def testfunc(n): count = ops.count("_GUARD_IS_TRUE_POP") + ops.count("_GUARD_IS_FALSE_POP") self.assertLessEqual(count, 2) -class TestUopsOptimization(unittest.TestCase): - - def _run_with_optimizer(self, testfunc, arg): - res = None - opt = _testinternalcapi.new_uop_optimizer() - with temporary_optimizer(opt): - res = testfunc(arg) - - ex = get_first_executor(testfunc) - return res, ex - - - def test_int_type_propagation(self): - def testfunc(loops): - num = 0 - for i in range(loops): - x = num + num - a = x + 1 - num += 1 - return a - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertIsNotNone(ex) - self.assertEqual(res, 63) - binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] - guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] - self.assertGreaterEqual(len(binop_count), 3) - self.assertLessEqual(len(guard_both_int_count), 1) - - def test_int_type_propagation_through_frame(self): - def double(x): - return x + x - def testfunc(loops): - num = 0 - for i in range(loops): - x = num + num - a = double(x) - num += 1 - return a - - opt = _testinternalcapi.new_uop_optimizer() - res = None - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - self.assertEqual(res, 124) - binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] - guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] - self.assertGreaterEqual(len(binop_count), 3) - self.assertLessEqual(len(guard_both_int_count), 1) - - def test_int_type_propagation_from_frame(self): - def double(x): - return x + x - def testfunc(loops): - num = 0 - for i in range(loops): - a = double(num) - x = a + a - num += 1 - return x - - opt = _testinternalcapi.new_uop_optimizer() - res = None - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - self.assertEqual(res, 124) - binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] - guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] - self.assertGreaterEqual(len(binop_count), 3) - self.assertLessEqual(len(guard_both_int_count), 1) - - def test_int_impure_region(self): - def testfunc(loops): - num = 0 - while num < loops: - x = num + num - y = 1 - x // 2 - a = x + y - num += 1 - return a - - res, ex = self._run_with_optimizer(testfunc, 64) - self.assertIsNotNone(ex) - binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] - self.assertGreaterEqual(len(binop_count), 3) - - def test_call_py_exact_args(self): - def testfunc(n): - def dummy(x): - return x+1 - for i in range(n): - dummy(i) - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - self.assertIn("_PUSH_FRAME", uops) - self.assertIn("_BINARY_OP_ADD_INT", uops) - self.assertNotIn("_CHECK_PEP_523", uops) - - def test_int_type_propagate_through_range(self): - def testfunc(n): - - for i in range(n): - x = i + i - return x - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertEqual(res, 62) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - self.assertNotIn("_GUARD_BOTH_INT", uops) - - def test_int_value_numbering(self): - def testfunc(n): - - y = 1 - for i in range(n): - x = y - z = x - a = z - b = a - res = x + z + a + b - return res - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertEqual(res, 4) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - self.assertIn("_GUARD_BOTH_INT", uops) - guard_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] - self.assertEqual(len(guard_count), 1) - - def test_comprehension(self): - def testfunc(n): - for _ in range(n): - return [i for i in range(n)] - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertEqual(res, list(range(32))) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - self.assertNotIn("_BINARY_OP_ADD_INT", uops) - - def test_call_py_exact_args_disappearing(self): - def dummy(x): - return x+1 - - def testfunc(n): - for i in range(n): - dummy(i) - - opt = _testinternalcapi.new_uop_optimizer() - # Trigger specialization - testfunc(8) - with temporary_optimizer(opt): - del dummy - gc.collect() - - def dummy(x): - return x + 2 - testfunc(32) - - ex = get_first_executor(testfunc) - # Honestly as long as it doesn't crash it's fine. - # Whether we get an executor or not is non-deterministic, - # because it's decided by when the function is freed. - # This test is a little implementation specific. - - def test_promote_globals_to_constants(self): - - result = script_helper.run_python_until_end('-c', textwrap.dedent(""" - import _testinternalcapi - import opcode - - def get_first_executor(func): - code = func.__code__ - co_code = code.co_code - JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] - for i in range(0, len(co_code), 2): - if co_code[i] == JUMP_BACKWARD: - try: - return _testinternalcapi.get_executor(code, i) - except ValueError: - pass - return None - - def get_opnames(ex): - return {item[0] for item in ex} - - def testfunc(n): - for i in range(n): - x = range(i) - return x - - opt = _testinternalcapi.new_uop_optimizer() - _testinternalcapi.set_optimizer(opt) - testfunc(64) - - ex = get_first_executor(testfunc) - assert ex is not None - uops = get_opnames(ex) - assert "_LOAD_GLOBAL_BUILTINS" not in uops - assert "_LOAD_CONST_INLINE_BORROW_WITH_NULL" in uops - """)) - self.assertEqual(result[0].rc, 0, result) - - def test_float_add_constant_propagation(self): - def testfunc(n): - a = 1.0 - for _ in range(n): - a = a + 0.1 - return a - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertAlmostEqual(res, 4.2) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] - self.assertLessEqual(len(guard_both_float_count), 1) - # TODO gh-115506: this assertion may change after propagating constants. - # We'll also need to verify that propagation actually occurs. - self.assertIn("_BINARY_OP_ADD_FLOAT", uops) - - def test_float_subtract_constant_propagation(self): - def testfunc(n): - a = 1.0 - for _ in range(n): - a = a - 0.1 - return a - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertAlmostEqual(res, -2.2) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] - self.assertLessEqual(len(guard_both_float_count), 1) - # TODO gh-115506: this assertion may change after propagating constants. - # We'll also need to verify that propagation actually occurs. - self.assertIn("_BINARY_OP_SUBTRACT_FLOAT", uops) - - def test_float_multiply_constant_propagation(self): - def testfunc(n): - a = 1.0 - for _ in range(n): - a = a * 2.0 - return a - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertAlmostEqual(res, 2 ** 32) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] - self.assertLessEqual(len(guard_both_float_count), 1) - # TODO gh-115506: this assertion may change after propagating constants. - # We'll also need to verify that propagation actually occurs. - self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops) - - def test_compare_op_type_propagation_float(self): - def testfunc(n): - a = 1.0 - for _ in range(n): - x = a == a - x = a == a - x = a == a - x = a == a - return x - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertTrue(res) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] - self.assertLessEqual(len(guard_both_float_count), 1) - self.assertIn("_COMPARE_OP_FLOAT", uops) - - def test_compare_op_type_propagation_int(self): - def testfunc(n): - a = 1 - for _ in range(n): - x = a == a - x = a == a - x = a == a - x = a == a - return x - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertTrue(res) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] - self.assertLessEqual(len(guard_both_float_count), 1) - self.assertIn("_COMPARE_OP_INT", uops) - - def test_compare_op_type_propagation_unicode(self): - def testfunc(n): - a = "" - for _ in range(n): - x = a == a - x = a == a - x = a == a - x = a == a - return x - - res, ex = self._run_with_optimizer(testfunc, 32) - self.assertTrue(res) - self.assertIsNotNone(ex) - uops = get_opnames(ex) - guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_UNICODE"] - self.assertLessEqual(len(guard_both_float_count), 1) - self.assertIn("_COMPARE_OP_STR", uops) if __name__ == "__main__": unittest.main() From f32824c984e0f7772e6b4c92a826f53e8f22996d Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:38:29 +0800 Subject: [PATCH 4/4] add unittest skipif --- Lib/test/test_capi/test_opt.py | 321 +++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 3f1ba984cfbc19..25fc36dec93ddc 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -4,6 +4,7 @@ import textwrap import unittest import gc +import os import _testinternalcapi @@ -569,5 +570,325 @@ def testfunc(n): self.assertLessEqual(count, 2) +@unittest.skipIf(os.getenv("PYTHONUOPSOPTIMIZE", default=0) == 0, "Needs uop optimizer to run.") +class TestUopsOptimization(unittest.TestCase): + + def _run_with_optimizer(self, testfunc, arg): + res = None + opt = _testinternalcapi.new_uop_optimizer() + with temporary_optimizer(opt): + res = testfunc(arg) + + ex = get_first_executor(testfunc) + return res, ex + + + def test_int_type_propagation(self): + def testfunc(loops): + num = 0 + for i in range(loops): + x = num + num + a = x + 1 + num += 1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertIsNotNone(ex) + self.assertEqual(res, 63) + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] + self.assertGreaterEqual(len(binop_count), 3) + self.assertLessEqual(len(guard_both_int_count), 1) + + def test_int_type_propagation_through_frame(self): + def double(x): + return x + x + def testfunc(loops): + num = 0 + for i in range(loops): + x = num + num + a = double(x) + num += 1 + return a + + opt = _testinternalcapi.new_uop_optimizer() + res = None + with temporary_optimizer(opt): + res = testfunc(32) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + self.assertEqual(res, 124) + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] + self.assertGreaterEqual(len(binop_count), 3) + self.assertLessEqual(len(guard_both_int_count), 1) + + def test_int_type_propagation_from_frame(self): + def double(x): + return x + x + def testfunc(loops): + num = 0 + for i in range(loops): + a = double(num) + x = a + a + num += 1 + return x + + opt = _testinternalcapi.new_uop_optimizer() + res = None + with temporary_optimizer(opt): + res = testfunc(32) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + self.assertEqual(res, 124) + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] + self.assertGreaterEqual(len(binop_count), 3) + self.assertLessEqual(len(guard_both_int_count), 1) + + def test_int_impure_region(self): + def testfunc(loops): + num = 0 + while num < loops: + x = num + num + y = 1 + x // 2 + a = x + y + num += 1 + return a + + res, ex = self._run_with_optimizer(testfunc, 64) + self.assertIsNotNone(ex) + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + self.assertGreaterEqual(len(binop_count), 3) + + def test_call_py_exact_args(self): + def testfunc(n): + def dummy(x): + return x+1 + for i in range(n): + dummy(i) + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_PUSH_FRAME", uops) + self.assertIn("_BINARY_OP_ADD_INT", uops) + self.assertNotIn("_CHECK_PEP_523", uops) + + def test_int_type_propagate_through_range(self): + def testfunc(n): + + for i in range(n): + x = i + i + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertEqual(res, 62) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_GUARD_BOTH_INT", uops) + + def test_int_value_numbering(self): + def testfunc(n): + + y = 1 + for i in range(n): + x = y + z = x + a = z + b = a + res = x + z + a + b + return res + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertEqual(res, 4) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_GUARD_BOTH_INT", uops) + guard_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] + self.assertEqual(len(guard_count), 1) + + def test_comprehension(self): + def testfunc(n): + for _ in range(n): + return [i for i in range(n)] + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertEqual(res, list(range(32))) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_BINARY_OP_ADD_INT", uops) + + def test_call_py_exact_args_disappearing(self): + def dummy(x): + return x+1 + + def testfunc(n): + for i in range(n): + dummy(i) + + opt = _testinternalcapi.new_uop_optimizer() + # Trigger specialization + testfunc(8) + with temporary_optimizer(opt): + del dummy + gc.collect() + + def dummy(x): + return x + 2 + testfunc(32) + + ex = get_first_executor(testfunc) + # Honestly as long as it doesn't crash it's fine. + # Whether we get an executor or not is non-deterministic, + # because it's decided by when the function is freed. + # This test is a little implementation specific. + + def test_promote_globals_to_constants(self): + + result = script_helper.run_python_until_end('-c', textwrap.dedent(""" + import _testinternalcapi + import opcode + + def get_first_executor(func): + code = func.__code__ + co_code = code.co_code + JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] + for i in range(0, len(co_code), 2): + if co_code[i] == JUMP_BACKWARD: + try: + return _testinternalcapi.get_executor(code, i) + except ValueError: + pass + return None + + def get_opnames(ex): + return {item[0] for item in ex} + + def testfunc(n): + for i in range(n): + x = range(i) + return x + + opt = _testinternalcapi.new_uop_optimizer() + _testinternalcapi.set_optimizer(opt) + testfunc(64) + + ex = get_first_executor(testfunc) + assert ex is not None + uops = get_opnames(ex) + assert "_LOAD_GLOBAL_BUILTINS" not in uops + assert "_LOAD_CONST_INLINE_BORROW_WITH_NULL" in uops + """)) + self.assertEqual(result[0].rc, 0, result) + + def test_float_add_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a + 0.1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, 4.2) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + # TODO gh-115506: this assertion may change after propagating constants. + # We'll also need to verify that propagation actually occurs. + self.assertIn("_BINARY_OP_ADD_FLOAT", uops) + + def test_float_subtract_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a - 0.1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, -2.2) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + # TODO gh-115506: this assertion may change after propagating constants. + # We'll also need to verify that propagation actually occurs. + self.assertIn("_BINARY_OP_SUBTRACT_FLOAT", uops) + + def test_float_multiply_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a * 2.0 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, 2 ** 32) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + # TODO gh-115506: this assertion may change after propagating constants. + # We'll also need to verify that propagation actually occurs. + self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops) + + def test_compare_op_type_propagation_float(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + x = a == a + x = a == a + x = a == a + x = a == a + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertTrue(res) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + self.assertIn("_COMPARE_OP_FLOAT", uops) + + def test_compare_op_type_propagation_int(self): + def testfunc(n): + a = 1 + for _ in range(n): + x = a == a + x = a == a + x = a == a + x = a == a + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertTrue(res) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] + self.assertLessEqual(len(guard_both_float_count), 1) + self.assertIn("_COMPARE_OP_INT", uops) + + def test_compare_op_type_propagation_unicode(self): + def testfunc(n): + a = "" + for _ in range(n): + x = a == a + x = a == a + x = a == a + x = a == a + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertTrue(res) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_UNICODE"] + self.assertLessEqual(len(guard_both_float_count), 1) + self.assertIn("_COMPARE_OP_STR", uops) + if __name__ == "__main__": unittest.main()