Issue #6690: Optimize the bytecode for expressions such as `x in {1, 2, 3}`,

where the right hand operand is a set of constants, by turning the set into
a frozenset and pre-building it as a constant.  The comparison operation
is made against the constant instead of building a new set each time it is
executed (a similar optimization already existed which turned a list of
constants into a pre-built tuple).  Patch and additional tests by Dave
Malcolm.
This commit is contained in:
Antoine Pitrou 2010-01-16 18:37:38 +00:00
parent a8f480f545
commit b7fbcd396f
3 changed files with 73 additions and 4 deletions

View File

@ -1,4 +1,5 @@
import dis
import re
import sys
from io import StringIO
import unittest
@ -115,6 +116,54 @@ class TestTranforms(unittest.TestCase):
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
],)
def test_folding_of_lists_of_constants(self):
for line, elem in (
# in/not in constants with BUILD_LIST should be folded to a tuple:
('a in [1,2,3]', '(1, 2, 3)'),
('a not in ["a","b","c"]', "(('a', 'b', 'c'))"),
('a in [None, 1, None]', '((None, 1, None))'),
('a not in [(1, 2), 3, 4]', '(((1, 2), 3, 4))'),
):
asm = dis_single(line)
self.assertIn(elem, asm)
self.assertNotIn('BUILD_LIST', asm)
def test_folding_of_sets_of_constants(self):
for line, elem in (
# in/not in constants with BUILD_SET should be folded to a frozenset:
('a in {1,2,3}', frozenset({1, 2, 3})),
('a not in {"a","b","c"}', frozenset({'a', 'c', 'b'})),
('a in {None, 1, None}', frozenset({1, None})),
('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
):
asm = dis_single(line)
self.assertNotIn('BUILD_SET', asm)
# Verify that the frozenset 'elem' is in the disassembly
# The ordering of the elements in repr( frozenset ) isn't
# guaranteed, so we jump through some hoops to ensure that we have
# the frozenset we expect:
self.assertIn('frozenset', asm)
# Extract the frozenset literal from the disassembly:
m = re.match(r'.*(frozenset\({.*}\)).*', asm, re.DOTALL)
self.assertTrue(m)
self.assertEqual(eval(m.group(1)), elem)
# Ensure that the resulting code actually works:
def f(a):
return a in {1, 2, 3}
def g(a):
return a not in {1, 2, 3}
self.assertTrue(f(3))
self.assertTrue(not f(4))
self.assertTrue(not g(3))
self.assertTrue(g(4))
def test_folding_of_binops_on_constants(self):
for line, elem in (
('a = 2+3+4', '(9)'), # chained fold

View File

@ -12,6 +12,14 @@ What's New in Python 3.2 Alpha 1?
Core and Builtins
-----------------
- Issue #6690: Optimize the bytecode for expressions such as `x in {1, 2, 3}`,
where the right hand operand is a set of constants, by turning the set into
a frozenset and pre-building it as a constant. The comparison operation
is made against the constant instead of building a new set each time it is
executed (a similar optimization already existed which turned a list of
constants into a pre-built tuple). Patch and additional tests by Dave
Malcolm.
- Issue #7622: Improve the split(), rsplit(), splitlines() and replace()
methods of bytes, bytearray and unicode objects by using a common
implementation based on stringlib's fast search. Patch by Florent Xicluna.

View File

@ -31,7 +31,8 @@
new constant (c1, c2, ... cn) can be appended.
Called with codestr pointing to the first LOAD_CONST.
Bails out with no change if one or more of the LOAD_CONSTs is missing.
Also works for BUILD_LIST when followed by an "in" or "not in" test.
Also works for BUILD_LIST and BUILT_SET when followed by an "in" or "not in"
test; for BUILD_SET it assembles a frozenset rather than a tuple.
*/
static int
tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
@ -41,7 +42,7 @@ tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
/* Pre-conditions */
assert(PyList_CheckExact(consts));
assert(codestr[n*3] == BUILD_TUPLE || codestr[n*3] == BUILD_LIST);
assert(codestr[n*3] == BUILD_TUPLE || codestr[n*3] == BUILD_LIST || codestr[n*3] == BUILD_SET);
assert(GETARG(codestr, (n*3)) == n);
for (i=0 ; i<n ; i++)
assert(codestr[i*3] == LOAD_CONST);
@ -59,6 +60,16 @@ tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
PyTuple_SET_ITEM(newconst, i, constant);
}
/* If it's a BUILD_SET, use the PyTuple we just built to create a
PyFrozenSet, and use that as the constant instead: */
if (codestr[n*3] == BUILD_SET) {
PyObject *tuple = newconst;
newconst = PyFrozenSet_New(tuple);
Py_DECREF(tuple);
if (newconst == NULL)
return 0;
}
/* Append folded constant onto consts */
if (PyList_Append(consts, newconst)) {
Py_DECREF(newconst);
@ -436,20 +447,21 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
cumlc = 0;
break;
/* Try to fold tuples of constants (includes a case for lists
/* Try to fold tuples of constants (includes a case for lists and sets
which are only used for "in" and "not in" tests).
Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
Replace BUILD_SEQN 2 UNPACK_SEQN 2 with ROT2.
Replace BUILD_SEQN 3 UNPACK_SEQN 3 with ROT3 ROT2. */
case BUILD_TUPLE:
case BUILD_LIST:
case BUILD_SET:
j = GETARG(codestr, i);
h = i - 3 * j;
if (h >= 0 &&
j <= lastlc &&
((opcode == BUILD_TUPLE &&
ISBASICBLOCK(blocks, h, 3*(j+1))) ||
(opcode == BUILD_LIST &&
((opcode == BUILD_LIST || opcode == BUILD_SET) &&
codestr[i+3]==COMPARE_OP &&
ISBASICBLOCK(blocks, h, 3*(j+2)) &&
(GETARG(codestr,i+3)==6 ||