Kaydet (Commit) b7fbcd39 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

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.
üst a8f480f5
import dis import dis
import re
import sys import sys
from io import StringIO from io import StringIO
import unittest import unittest
...@@ -115,6 +116,54 @@ class TestTranforms(unittest.TestCase): ...@@ -115,6 +116,54 @@ class TestTranforms(unittest.TestCase):
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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): def test_folding_of_binops_on_constants(self):
for line, elem in ( for line, elem in (
('a = 2+3+4', '(9)'), # chained fold ('a = 2+3+4', '(9)'), # chained fold
......
...@@ -12,6 +12,14 @@ What's New in Python 3.2 Alpha 1? ...@@ -12,6 +12,14 @@ What's New in Python 3.2 Alpha 1?
Core and Builtins 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() - Issue #7622: Improve the split(), rsplit(), splitlines() and replace()
methods of bytes, bytearray and unicode objects by using a common methods of bytes, bytearray and unicode objects by using a common
implementation based on stringlib's fast search. Patch by Florent Xicluna. implementation based on stringlib's fast search. Patch by Florent Xicluna.
......
...@@ -31,7 +31,8 @@ ...@@ -31,7 +31,8 @@
new constant (c1, c2, ... cn) can be appended. new constant (c1, c2, ... cn) can be appended.
Called with codestr pointing to the first LOAD_CONST. Called with codestr pointing to the first LOAD_CONST.
Bails out with no change if one or more of the LOAD_CONSTs is missing. 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 static int
tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts) 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) ...@@ -41,7 +42,7 @@ tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
/* Pre-conditions */ /* Pre-conditions */
assert(PyList_CheckExact(consts)); 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); assert(GETARG(codestr, (n*3)) == n);
for (i=0 ; i<n ; i++) for (i=0 ; i<n ; i++)
assert(codestr[i*3] == LOAD_CONST); assert(codestr[i*3] == LOAD_CONST);
...@@ -59,6 +60,16 @@ tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts) ...@@ -59,6 +60,16 @@ tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
PyTuple_SET_ITEM(newconst, i, constant); 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 */ /* Append folded constant onto consts */
if (PyList_Append(consts, newconst)) { if (PyList_Append(consts, newconst)) {
Py_DECREF(newconst); Py_DECREF(newconst);
...@@ -436,20 +447,21 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, ...@@ -436,20 +447,21 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
cumlc = 0; cumlc = 0;
break; 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). which are only used for "in" and "not in" tests).
Skip over BUILD_SEQN 1 UNPACK_SEQN 1. Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
Replace BUILD_SEQN 2 UNPACK_SEQN 2 with ROT2. Replace BUILD_SEQN 2 UNPACK_SEQN 2 with ROT2.
Replace BUILD_SEQN 3 UNPACK_SEQN 3 with ROT3 ROT2. */ Replace BUILD_SEQN 3 UNPACK_SEQN 3 with ROT3 ROT2. */
case BUILD_TUPLE: case BUILD_TUPLE:
case BUILD_LIST: case BUILD_LIST:
case BUILD_SET:
j = GETARG(codestr, i); j = GETARG(codestr, i);
h = i - 3 * j; h = i - 3 * j;
if (h >= 0 && if (h >= 0 &&
j <= lastlc && j <= lastlc &&
((opcode == BUILD_TUPLE && ((opcode == BUILD_TUPLE &&
ISBASICBLOCK(blocks, h, 3*(j+1))) || ISBASICBLOCK(blocks, h, 3*(j+1))) ||
(opcode == BUILD_LIST && ((opcode == BUILD_LIST || opcode == BUILD_SET) &&
codestr[i+3]==COMPARE_OP && codestr[i+3]==COMPARE_OP &&
ISBASICBLOCK(blocks, h, 3*(j+2)) && ISBASICBLOCK(blocks, h, 3*(j+2)) &&
(GETARG(codestr,i+3)==6 || (GETARG(codestr,i+3)==6 ||
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment