diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2010-01-16 18:37:38 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2010-01-16 18:37:38 (GMT) |
commit | b7fbcd396fc4a366433cf6f26cae64fecb056099 (patch) | |
tree | 15efa75386a0f8e2d8381267a0c2edd6b551aac3 | |
parent | a8f480f54597cf20e460b12e17bb0416a8008868 (diff) | |
download | cpython-b7fbcd396fc4a366433cf6f26cae64fecb056099.zip cpython-b7fbcd396fc4a366433cf6f26cae64fecb056099.tar.gz cpython-b7fbcd396fc4a366433cf6f26cae64fecb056099.tar.bz2 |
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.
-rw-r--r-- | Lib/test/test_peepholer.py | 49 | ||||
-rw-r--r-- | Misc/NEWS | 8 | ||||
-rw-r--r-- | Python/peephole.c | 20 |
3 files changed, 73 insertions, 4 deletions
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 9e8bb69..fcd7ccc 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -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 @@ -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. diff --git a/Python/peephole.c b/Python/peephole.c index 104db8c..eeb31d5 100644 --- a/Python/peephole.c +++ b/Python/peephole.c @@ -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 || |