Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b7fbcd3

Browse files
committed
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.
1 parent a8f480f commit b7fbcd3

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

Lib/test/test_peepholer.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import dis
2+
import re
23
import sys
34
from io import StringIO
45
import unittest
@@ -115,6 +116,54 @@ def crater():
115116
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
116117
],)
117118

119+
def test_folding_of_lists_of_constants(self):
120+
for line, elem in (
121+
# in/not in constants with BUILD_LIST should be folded to a tuple:
122+
('a in [1,2,3]', '(1, 2, 3)'),
123+
('a not in ["a","b","c"]', "(('a', 'b', 'c'))"),
124+
('a in [None, 1, None]', '((None, 1, None))'),
125+
('a not in [(1, 2), 3, 4]', '(((1, 2), 3, 4))'),
126+
):
127+
asm = dis_single(line)
128+
self.assertIn(elem, asm)
129+
self.assertNotIn('BUILD_LIST', asm)
130+
131+
def test_folding_of_sets_of_constants(self):
132+
for line, elem in (
133+
# in/not in constants with BUILD_SET should be folded to a frozenset:
134+
('a in {1,2,3}', frozenset({1, 2, 3})),
135+
('a not in {"a","b","c"}', frozenset({'a', 'c', 'b'})),
136+
('a in {None, 1, None}', frozenset({1, None})),
137+
('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
138+
('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
139+
):
140+
asm = dis_single(line)
141+
self.assertNotIn('BUILD_SET', asm)
142+
143+
# Verify that the frozenset 'elem' is in the disassembly
144+
# The ordering of the elements in repr( frozenset ) isn't
145+
# guaranteed, so we jump through some hoops to ensure that we have
146+
# the frozenset we expect:
147+
self.assertIn('frozenset', asm)
148+
# Extract the frozenset literal from the disassembly:
149+
m = re.match(r'.*(frozenset\({.*}\)).*', asm, re.DOTALL)
150+
self.assertTrue(m)
151+
self.assertEqual(eval(m.group(1)), elem)
152+
153+
# Ensure that the resulting code actually works:
154+
def f(a):
155+
return a in {1, 2, 3}
156+
157+
def g(a):
158+
return a not in {1, 2, 3}
159+
160+
self.assertTrue(f(3))
161+
self.assertTrue(not f(4))
162+
163+
self.assertTrue(not g(3))
164+
self.assertTrue(g(4))
165+
166+
118167
def test_folding_of_binops_on_constants(self):
119168
for line, elem in (
120169
('a = 2+3+4', '(9)'), # chained fold

Misc/NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ What's New in Python 3.2 Alpha 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #6690: Optimize the bytecode for expressions such as `x in {1, 2, 3}`,
16+
where the right hand operand is a set of constants, by turning the set into
17+
a frozenset and pre-building it as a constant. The comparison operation
18+
is made against the constant instead of building a new set each time it is
19+
executed (a similar optimization already existed which turned a list of
20+
constants into a pre-built tuple). Patch and additional tests by Dave
21+
Malcolm.
22+
1523
- Issue #7622: Improve the split(), rsplit(), splitlines() and replace()
1624
methods of bytes, bytearray and unicode objects by using a common
1725
implementation based on stringlib's fast search. Patch by Florent Xicluna.

Python/peephole.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
new constant (c1, c2, ... cn) can be appended.
3232
Called with codestr pointing to the first LOAD_CONST.
3333
Bails out with no change if one or more of the LOAD_CONSTs is missing.
34-
Also works for BUILD_LIST when followed by an "in" or "not in" test.
34+
Also works for BUILD_LIST and BUILT_SET when followed by an "in" or "not in"
35+
test; for BUILD_SET it assembles a frozenset rather than a tuple.
3536
*/
3637
static int
3738
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)
4142

4243
/* Pre-conditions */
4344
assert(PyList_CheckExact(consts));
44-
assert(codestr[n*3] == BUILD_TUPLE || codestr[n*3] == BUILD_LIST);
45+
assert(codestr[n*3] == BUILD_TUPLE || codestr[n*3] == BUILD_LIST || codestr[n*3] == BUILD_SET);
4546
assert(GETARG(codestr, (n*3)) == n);
4647
for (i=0 ; i<n ; i++)
4748
assert(codestr[i*3] == LOAD_CONST);
@@ -59,6 +60,16 @@ tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
5960
PyTuple_SET_ITEM(newconst, i, constant);
6061
}
6162

63+
/* If it's a BUILD_SET, use the PyTuple we just built to create a
64+
PyFrozenSet, and use that as the constant instead: */
65+
if (codestr[n*3] == BUILD_SET) {
66+
PyObject *tuple = newconst;
67+
newconst = PyFrozenSet_New(tuple);
68+
Py_DECREF(tuple);
69+
if (newconst == NULL)
70+
return 0;
71+
}
72+
6273
/* Append folded constant onto consts */
6374
if (PyList_Append(consts, newconst)) {
6475
Py_DECREF(newconst);
@@ -436,20 +447,21 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
436447
cumlc = 0;
437448
break;
438449

439-
/* Try to fold tuples of constants (includes a case for lists
450+
/* Try to fold tuples of constants (includes a case for lists and sets
440451
which are only used for "in" and "not in" tests).
441452
Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
442453
Replace BUILD_SEQN 2 UNPACK_SEQN 2 with ROT2.
443454
Replace BUILD_SEQN 3 UNPACK_SEQN 3 with ROT3 ROT2. */
444455
case BUILD_TUPLE:
445456
case BUILD_LIST:
457+
case BUILD_SET:
446458
j = GETARG(codestr, i);
447459
h = i - 3 * j;
448460
if (h >= 0 &&
449461
j <= lastlc &&
450462
((opcode == BUILD_TUPLE &&
451463
ISBASICBLOCK(blocks, h, 3*(j+1))) ||
452-
(opcode == BUILD_LIST &&
464+
((opcode == BUILD_LIST || opcode == BUILD_SET) &&
453465
codestr[i+3]==COMPARE_OP &&
454466
ISBASICBLOCK(blocks, h, 3*(j+2)) &&
455467
(GETARG(codestr,i+3)==6 ||

0 commit comments

Comments
 (0)