Skip to content

Commit 33baf2c

Browse files
committed
Eliminate redundant refcounting from UNPACK_SEQUENCE family
1 parent e22c495 commit 33baf2c

File tree

9 files changed

+263
-98
lines changed

9 files changed

+263
-98
lines changed

Include/internal/pycore_opcode_metadata.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_ids.h

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_capi/test_opt.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,75 @@ def testfunc(n, m):
433433
uops = get_opnames(ex)
434434
self.assertIn("_FOR_ITER_TIER_TWO", uops)
435435

436+
def test_unpack_sequence_generic(self):
437+
def testfunc(x):
438+
i = 0
439+
while i < x:
440+
i += 1
441+
# Use an iterator to force generic UNPACK_SEQUENCE
442+
a, b = iter((1, 2))
443+
return a, b
444+
445+
res = testfunc(TIER2_THRESHOLD)
446+
self.assertEqual(res, (1, 2))
447+
448+
ex = get_first_executor(testfunc)
449+
self.assertIsNotNone(ex)
450+
uops = get_opnames(ex)
451+
self.assertIn("_UNPACK_SEQUENCE", uops)
452+
453+
def test_unpack_sequence_two_tuple(self):
454+
def testfunc(x):
455+
i = 0
456+
while i < x:
457+
i += 1
458+
t = (i, i)
459+
a, b = t
460+
return a, b
461+
462+
res = testfunc(TIER2_THRESHOLD)
463+
self.assertEqual(res, (TIER2_THRESHOLD, TIER2_THRESHOLD))
464+
465+
ex = get_first_executor(testfunc)
466+
self.assertIsNotNone(ex)
467+
uops = get_opnames(ex)
468+
self.assertIn("_UNPACK_SEQUENCE_TWO_TUPLE", uops)
469+
self.assertNotIn("_POP_TOP", uops)
470+
471+
def test_unpack_sequence_tuple(self):
472+
def testfunc(x):
473+
i = 0
474+
while i < x:
475+
i += 1
476+
t = (i, i, i)
477+
a, b, c = t
478+
return a, b, c
479+
480+
res = testfunc(TIER2_THRESHOLD)
481+
self.assertEqual(res, (TIER2_THRESHOLD, TIER2_THRESHOLD, TIER2_THRESHOLD))
482+
483+
ex = get_first_executor(testfunc)
484+
self.assertIsNotNone(ex)
485+
uops = get_opnames(ex)
486+
self.assertIn("_UNPACK_SEQUENCE_TUPLE", uops)
487+
self.assertNotIn("_POP_TOP", uops)
488+
489+
def test_unpack_sequence_list(self):
490+
def testfunc(x):
491+
i = 0
492+
while i < x:
493+
i += 1
494+
a, b = [1, 2]
495+
return a, b
496+
497+
res = testfunc(TIER2_THRESHOLD)
498+
self.assertEqual(res, (1, 2))
499+
500+
ex = get_first_executor(testfunc)
501+
self.assertIsNotNone(ex)
502+
uops = get_opnames(ex)
503+
self.assertIn("_UNPACK_SEQUENCE_LIST", uops)
504+
436505

437506
@requires_specialization
438507
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")

Python/bytecodes.c

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,33 +1576,38 @@ dummy_func(
15761576
(void)counter;
15771577
}
15781578

1579-
op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0])) {
1580-
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
1579+
op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0], s)) {
1580+
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
15811581
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg, -1, top);
1582-
Py_DECREF(seq_o);
1583-
ERROR_IF(res == 0);
1582+
if (res == 0) {
1583+
PyStackRef_CLOSE(seq);
1584+
ERROR_IF(1);
1585+
}
1586+
s = seq;
1587+
INPUTS_DEAD();
15841588
}
15851589

1586-
macro(UNPACK_SEQUENCE) = _SPECIALIZE_UNPACK_SEQUENCE + _UNPACK_SEQUENCE;
1590+
macro(UNPACK_SEQUENCE) = _SPECIALIZE_UNPACK_SEQUENCE + _UNPACK_SEQUENCE + POP_TOP;
15871591

15881592
macro(UNPACK_SEQUENCE_TWO_TUPLE) =
1589-
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TWO_TUPLE;
1593+
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TWO_TUPLE + POP_TOP;
15901594

1591-
op(_UNPACK_SEQUENCE_TWO_TUPLE, (seq -- val1, val0)) {
1595+
op(_UNPACK_SEQUENCE_TWO_TUPLE, (seq -- val1, val0, s)) {
15921596
assert(oparg == 2);
15931597
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
15941598
assert(PyTuple_CheckExact(seq_o));
15951599
DEOPT_IF(PyTuple_GET_SIZE(seq_o) != 2);
15961600
STAT_INC(UNPACK_SEQUENCE, hit);
15971601
val0 = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq_o, 0));
15981602
val1 = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq_o, 1));
1599-
PyStackRef_CLOSE(seq);
1603+
s = seq;
1604+
INPUTS_DEAD();
16001605
}
16011606

16021607
macro(UNPACK_SEQUENCE_TUPLE) =
1603-
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TUPLE;
1608+
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TUPLE + POP_TOP;
16041609

1605-
op(_UNPACK_SEQUENCE_TUPLE, (seq -- values[oparg])) {
1610+
op(_UNPACK_SEQUENCE_TUPLE, (seq -- values[oparg], s)) {
16061611
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
16071612
assert(PyTuple_CheckExact(seq_o));
16081613
DEOPT_IF(PyTuple_GET_SIZE(seq_o) != oparg);
@@ -1611,13 +1616,14 @@ dummy_func(
16111616
for (int i = oparg; --i >= 0; ) {
16121617
*values++ = PyStackRef_FromPyObjectNew(items[i]);
16131618
}
1614-
DECREF_INPUTS();
1619+
s = seq;
1620+
INPUTS_DEAD();
16151621
}
16161622

16171623
macro(UNPACK_SEQUENCE_LIST) =
1618-
_GUARD_TOS_LIST + unused/1 + _UNPACK_SEQUENCE_LIST;
1624+
_GUARD_TOS_LIST + unused/1 + _UNPACK_SEQUENCE_LIST + POP_TOP;
16191625

1620-
op(_UNPACK_SEQUENCE_LIST, (seq -- values[oparg])) {
1626+
op(_UNPACK_SEQUENCE_LIST, (seq -- values[oparg], s)) {
16211627
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
16221628
assert(PyList_CheckExact(seq_o));
16231629
DEOPT_IF(!LOCK_OBJECT(seq_o));
@@ -1631,7 +1637,8 @@ dummy_func(
16311637
*values++ = PyStackRef_FromPyObjectNew(items[i]);
16321638
}
16331639
UNLOCK_OBJECT(seq_o);
1634-
DECREF_INPUTS();
1640+
s = seq;
1641+
INPUTS_DEAD();
16351642
}
16361643

16371644
inst(UNPACK_EX, (seq -- unused[oparg & 0xFF], unused, unused[oparg >> 8], top[0])) {

0 commit comments

Comments
 (0)