Speed up with statements by storing the __exit__ method on the stack instead of in a temp variable (bumps the magic number for pyc files)

This commit is contained in:
Nick Coghlan 2008-03-07 14:13:28 +00:00
parent e75f59a578
commit 7af53be66f
6 changed files with 77 additions and 55 deletions

View File

@ -519,21 +519,24 @@ Miscellaneous opcodes.
.. opcode:: WITH_CLEANUP () .. opcode:: WITH_CLEANUP ()
Cleans up the stack when a :keyword:`with` statement block exits. TOS is the Cleans up the stack when a :keyword:`with` statement block exits. On top of
context manager's :meth:`__exit__` bound method. Below that are 1--3 values the stack are 1--3 values indicating how/why the finally clause was entered:
indicating how/why the finally clause was entered:
* SECOND = ``None`` * TOP = ``None``
* (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval * (TOP, SECOND) = (``WHY_{RETURN,CONTINUE}``), retval
* SECOND = ``WHY_*``; no retval below it * TOP = ``WHY_*``; no retval below it
* (SECOND, THIRD, FOURTH) = exc_info() * (TOP, SECOND, THIRD) = exc_info()
In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise Under them is EXIT, the context manager's :meth:`__exit__` bound method.
``TOS(None, None, None)``.
In addition, if the stack represents an exception, *and* the function call In the last case, ``EXIT(TOP, SECOND, THIRD)`` is called, otherwise
returns a 'true' value, this information is "zapped", to prevent ``END_FINALLY`` ``EXIT(None, None, None)``.
from re-raising the exception. (But non-local gotos should still be resumed.)
EXIT is removed from the stack, leaving the values above it in the same
order. In addition, if the stack represents an exception, *and* the function
call returns a 'true' value, this information is "zapped", to prevent
``END_FINALLY`` from re-raising the exception. (But non-local gotos should
still be resumed.)
.. XXX explain the WHY stuff! .. XXX explain the WHY stuff!

View File

@ -822,14 +822,13 @@ class CodeGenerator:
def visitWith(self, node): def visitWith(self, node):
body = self.newBlock() body = self.newBlock()
final = self.newBlock() final = self.newBlock()
exitvar = "$exit%d" % self.__with_count
valuevar = "$value%d" % self.__with_count valuevar = "$value%d" % self.__with_count
self.__with_count += 1 self.__with_count += 1
self.set_lineno(node) self.set_lineno(node)
self.visit(node.expr) self.visit(node.expr)
self.emit('DUP_TOP') self.emit('DUP_TOP')
self.emit('LOAD_ATTR', '__exit__') self.emit('LOAD_ATTR', '__exit__')
self._implicitNameOp('STORE', exitvar) self.emit('ROT_TWO')
self.emit('LOAD_ATTR', '__enter__') self.emit('LOAD_ATTR', '__enter__')
self.emit('CALL_FUNCTION', 0) self.emit('CALL_FUNCTION', 0)
if node.vars is None: if node.vars is None:
@ -849,8 +848,6 @@ class CodeGenerator:
self.emit('LOAD_CONST', None) self.emit('LOAD_CONST', None)
self.nextBlock(final) self.nextBlock(final)
self.setups.push((END_FINALLY, final)) self.setups.push((END_FINALLY, final))
self._implicitNameOp('LOAD', exitvar)
self._implicitNameOp('DELETE', exitvar)
self.emit('WITH_CLEANUP') self.emit('WITH_CLEANUP')
self.emit('END_FINALLY') self.emit('END_FINALLY')
self.setups.pop() self.setups.pop()

View File

@ -12,6 +12,9 @@ What's New in Python 2.6 alpha 2?
Core and builtins Core and builtins
----------------- -----------------
- Issue #2179: speed up with statement execution by storing the exit method
on the stack instead of in a temporary variable (patch by Jeffrey Yaskin)
- Issue #2238: Some syntax errors in *args and **kwargs expressions could give - Issue #2238: Some syntax errors in *args and **kwargs expressions could give
bogus error messages. bogus error messages.

View File

@ -2254,17 +2254,20 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
case WITH_CLEANUP: case WITH_CLEANUP:
{ {
/* TOP is the context.__exit__ bound method. /* At the top of the stack are 1-3 values indicating
Below that are 1-3 values indicating how/why how/why we entered the finally clause:
we entered the finally clause: - TOP = None
- SECOND = None - (TOP, SECOND) = (WHY_{RETURN,CONTINUE}), retval
- (SECOND, THIRD) = (WHY_{RETURN,CONTINUE}), retval - TOP = WHY_*; no retval below it
- SECOND = WHY_*; no retval below it - (TOP, SECOND, THIRD) = exc_info()
- (SECOND, THIRD, FOURTH) = exc_info() Below them is EXIT, the context.__exit__ bound method.
In the last case, we must call In the last case, we must call
TOP(SECOND, THIRD, FOURTH) EXIT(TOP, SECOND, THIRD)
otherwise we must call otherwise we must call
TOP(None, None, None) EXIT(None, None, None)
In all cases, we remove EXIT from the stack, leaving
the rest in the same order.
In addition, if the stack represents an exception, In addition, if the stack represents an exception,
*and* the function call returns a 'true' value, we *and* the function call returns a 'true' value, we
@ -2273,36 +2276,59 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
should still be resumed.) should still be resumed.)
*/ */
x = TOP(); PyObject *exit_func;
u = SECOND();
if (PyInt_Check(u) || u == Py_None) { u = POP();
if (u == Py_None) {
exit_func = TOP();
SET_TOP(u);
v = w = Py_None;
}
else if (PyInt_Check(u)) {
switch(PyInt_AS_LONG(u)) {
case WHY_RETURN:
case WHY_CONTINUE:
/* Retval in TOP. */
exit_func = SECOND();
SET_SECOND(TOP());
SET_TOP(u);
break;
default:
exit_func = TOP();
SET_TOP(u);
break;
}
u = v = w = Py_None; u = v = w = Py_None;
} }
else { else {
v = THIRD(); v = TOP();
w = FOURTH(); w = SECOND();
exit_func = THIRD();
SET_TOP(u);
SET_SECOND(v);
SET_THIRD(w);
} }
/* XXX Not the fastest way to call it... */ /* XXX Not the fastest way to call it... */
x = PyObject_CallFunctionObjArgs(x, u, v, w, NULL); x = PyObject_CallFunctionObjArgs(exit_func, u, v, w,
if (x == NULL) NULL);
if (x == NULL) {
Py_DECREF(exit_func);
break; /* Go to error exit */ break; /* Go to error exit */
}
if (u != Py_None && PyObject_IsTrue(x)) { if (u != Py_None && PyObject_IsTrue(x)) {
/* There was an exception and a true return */ /* There was an exception and a true return */
Py_DECREF(x); STACKADJ(-2);
x = TOP(); /* Again */
STACKADJ(-3);
Py_INCREF(Py_None); Py_INCREF(Py_None);
SET_TOP(Py_None); SET_TOP(Py_None);
Py_DECREF(x);
Py_DECREF(u); Py_DECREF(u);
Py_DECREF(v); Py_DECREF(v);
Py_DECREF(w); Py_DECREF(w);
} else { } else {
/* Let END_FINALLY do its thing */ /* The stack was rearranged to remove EXIT
Py_DECREF(x); above. Let END_FINALLY do its thing */
x = POP();
Py_DECREF(x);
} }
Py_DECREF(x);
Py_DECREF(exit_func);
PREDICT(END_FINALLY); PREDICT(END_FINALLY);
break; break;
} }

View File

@ -2842,7 +2842,7 @@ compiler_with(struct compiler *c, stmt_ty s)
{ {
static identifier enter_attr, exit_attr; static identifier enter_attr, exit_attr;
basicblock *block, *finally; basicblock *block, *finally;
identifier tmpexit, tmpvalue = NULL; identifier tmpvalue = NULL;
assert(s->kind == With_kind); assert(s->kind == With_kind);
@ -2862,12 +2862,6 @@ compiler_with(struct compiler *c, stmt_ty s)
if (!block || !finally) if (!block || !finally)
return 0; return 0;
/* Create a temporary variable to hold context.__exit__ */
tmpexit = compiler_new_tmpname(c);
if (tmpexit == NULL)
return 0;
PyArena_AddPyObject(c->c_arena, tmpexit);
if (s->v.With.optional_vars) { if (s->v.With.optional_vars) {
/* Create a temporary variable to hold context.__enter__(). /* Create a temporary variable to hold context.__enter__().
We need to do this rather than preserving it on the stack We need to do this rather than preserving it on the stack
@ -2887,11 +2881,10 @@ compiler_with(struct compiler *c, stmt_ty s)
/* Evaluate EXPR */ /* Evaluate EXPR */
VISIT(c, expr, s->v.With.context_expr); VISIT(c, expr, s->v.With.context_expr);
/* Squirrel away context.__exit__ */ /* Squirrel away context.__exit__ by stuffing it under context */
ADDOP(c, DUP_TOP); ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_ATTR, exit_attr, names); ADDOP_O(c, LOAD_ATTR, exit_attr, names);
if (!compiler_nameop(c, tmpexit, Store)) ADDOP(c, ROT_TWO);
return 0;
/* Call context.__enter__() */ /* Call context.__enter__() */
ADDOP_O(c, LOAD_ATTR, enter_attr, names); ADDOP_O(c, LOAD_ATTR, enter_attr, names);
@ -2935,10 +2928,9 @@ compiler_with(struct compiler *c, stmt_ty s)
if (!compiler_push_fblock(c, FINALLY_END, finally)) if (!compiler_push_fblock(c, FINALLY_END, finally))
return 0; return 0;
/* Finally block starts; push tmpexit and issue our magic opcode. */ /* Finally block starts; context.__exit__ is on the stack under
if (!compiler_nameop(c, tmpexit, Load) || the exception or return information. Just issue our magic
!compiler_nameop(c, tmpexit, Del)) opcode. */
return 0;
ADDOP(c, WITH_CLEANUP); ADDOP(c, WITH_CLEANUP);
/* Finally block ends. */ /* Finally block ends. */

View File

@ -72,9 +72,10 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
storing constants that should have been removed) storing constants that should have been removed)
Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp) Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp)
Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode) Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)
Python 2.6a1: 62161 (WITH_CLEANUP optimization)
. .
*/ */
#define MAGIC (62151 | ((long)'\r'<<16) | ((long)'\n'<<24)) #define MAGIC (62161 | ((long)'\r'<<16) | ((long)'\n'<<24))
/* Magic word as global; note that _PyImport_Init() can change the /* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the value of this global to accommodate for alterations of how the