On Mon, Sep 2, 2019 at 6:31 PM Alan Bawden <a...@csail.mit.edu> wrote: > > Dang! There is exactly the instruction sequence I just argued could be > optimized away still sitting right there. So maybe my belief that this is > being done by peephole optimization is in fact incorrect? So I went and > tried again: > > bash-4.2$ python3 -E > Python 3.6.6 (default, Aug 13 2018, 18:24:23) > [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux > Type "help", "copyright", "credits" or "license" for more information. > >>> def f(): > ... a, b = 2, 3 > ... a, b = [2, 3] > ... a, b = b, a > ... a, b = [b, a] > ... > >>> import dis > >>> dis.dis(f) > 2 0 LOAD_CONST 3 ((2, 3)) > 2 UNPACK_SEQUENCE 2 > 4 STORE_FAST 0 (a) > 6 STORE_FAST 1 (b) > > 3 8 LOAD_CONST 1 (2) > 10 LOAD_CONST 2 (3) > 12 ROT_TWO > 14 STORE_FAST 0 (a) > 16 STORE_FAST 1 (b) > > 4 18 LOAD_FAST 1 (b) > 20 LOAD_FAST 0 (a) > 22 ROT_TWO > 24 STORE_FAST 0 (a) > 26 STORE_FAST 1 (b) > > 5 28 LOAD_FAST 1 (b) > 30 LOAD_FAST 0 (a) > 32 ROT_TWO > 34 STORE_FAST 0 (a) > 36 STORE_FAST 1 (b) > 38 LOAD_CONST 0 (None) > 40 RETURN_VALUE > > OK, now I'm confused. How come I'm not seeing the > BUILD_LIST/UNPACK_SEQUENCE sequence that you're seeing? > > > This is with CPython 3.9. It's entirely possible that other Pythons > > and/or other versions of CPython may give different results, but with > > this particular interpreter, the list is not optimized away. > > I actually did try this with several different versions of CPython going > back to 2.4 and up to 3.6, and they all behave this way for me. Maybe > something changed after 3.6? Weird...
This is indeed fascinating. Something DID indeed change. I tried this slightly shorter version in a few different Pythons: def f(): a, b = b, a a, b = [b, a] import dis, sys print(sys.version) dis.dis(f) # Show the code identically on 2.x and 3.x print(repr(f.__code__.co_code).lstrip("b")) Here's what I learned: CPython 2.7: ROT_TWO, bytecode CPython 3.4: ROT_TWO, bytecode CPython 3.5: ROT_TWO, bytecode CPython 3.6: ROT_TWO, wordcode CPython 3.7: UNPACK_SEQUENCE CPython 3.8: UNPACK_SEQUENCE CPython 3.9: UNPACK_SEQUENCE PyPy 5.6 (2.7): out-of-order LOAD/STORE PyPyJS (2.7.9): out-of-order LOAD/STORE Jython 2.5.3: unable to disassemble MicroPython 3.4: no 'dis' module or __code__ attr Brython: unable to disassemble CPython 3.6 made the change to wordcode. If you have a 3.5 hanging around, you should be able to see this easily in the disassembly, because the LOAD_FAST operations require three bytes each in 3.5, but only one (two byte) operation in 3.6, but the ROT_TWO is a single byte in 3.5 and now requires two in 3.6. PyPy can reorder operations knowing that it won't affect anything, and thus optimizes it down to "load b, load a, store b, store a" regardless of the syntax. But the curious difference happens in 3.7. I don't know what changed to cause this, but from there on, the list gets built and then unpacked. This may represent a performance regression. Alternatively, just take it as a recommendation to always do your variable exchanges WITHOUT square brackets, and you'll be fine. ChrisA -- https://mail.python.org/mailman/listinfo/python-list