Still somewhat experimental speedup. This appears to speed up the

most common interface to Tcl, the call() method, by maybe 20-25%.

The speedup code avoids the construction of a Tcl command string from
the argument list -- the Tcl argument list is immediately parsed back
by Tcl_Eval() into a list that is *guaranteed* (by Tcl_Merge()) to be
exactly the same list, so instead we look up the command info and call
the command function directly.  If the lookup fails, we fall back to
the old method (Tcl_Merge() + Tcl_Eval()) so we don't need to worry
about special cases like undefined commands or the occasional command
("after") that sets the info.proc pointer to NULL -- let TclEval()
deal with these.
This commit is contained in:
Guido van Rossum 1998-04-29 16:22:14 +00:00
parent 9d1b7ae65b
commit 212643f199
1 changed files with 110 additions and 9 deletions

View File

@ -43,6 +43,15 @@ PERFORMANCE OF THIS SOFTWARE.
Use Tcl 8.0 if available (even alpha or beta). Use Tcl 8.0 if available (even alpha or beta).
The oldest usable version is 4.1p1/7.5p1. The oldest usable version is 4.1p1/7.5p1.
XXX Further speed-up ideas, involving Tcl 8.0 features:
- In Tcl_Call(), create Tcl objects from the arguments, possibly using
intelligent mappings between Python objects and Tcl objects (e.g. ints,
floats and Tcl window pointers could be handled specially).
- Register a new Tcl type, "Python callable", which can be called more
efficiently and passed to Tcl_EvalObj() directly (if this is possible).
*/ */
@ -395,21 +404,107 @@ Tkapp_Call(self, args)
PyObject *self; PyObject *self;
PyObject *args; PyObject *args;
{ {
char *cmd = Merge(args); /* This is copied from Merge() */
PyObject *res = NULL; PyObject *tmp = NULL;
char *argvStore[ARGSZ];
char **argv = NULL;
int fvStore[ARGSZ];
int *fv = NULL;
int argc = 0, i;
PyObject *res = NULL; /* except this has a different type */
Tcl_CmdInfo info; /* and this is added */
Tcl_Interp *interp = Tkapp_Interp(self); /* and this too */
if (!cmd) if (!(tmp = PyList_New(0)))
PyErr_SetString(Tkinter_TclError, "merge failed"); return NULL;
else if (Tcl_Eval(Tkapp_Interp(self), cmd) == TCL_ERROR) argv = argvStore;
res = Tkinter_Error(self); fv = fvStore;
else if (args == NULL)
res = PyString_FromString(Tkapp_Result(self)); argc = 0;
if (cmd) else if (!PyTuple_Check(args)) {
argc = 1;
fv[0] = 0;
argv[0] = AsString(args, tmp);
}
else {
argc = PyTuple_Size(args);
if (argc > ARGSZ) {
argv = (char **)ckalloc(argc * sizeof(char *));
fv = (int *)ckalloc(argc * sizeof(int));
if (argv == NULL || fv == NULL) {
PyErr_NoMemory();
goto finally;
}
}
for (i = 0; i < argc; i++) {
PyObject *v = PyTuple_GetItem(args, i);
if (PyTuple_Check(v)) {
fv[i] = 1;
if (!(argv[i] = Merge(v)))
goto finally;
}
else if (v == Py_None) {
argc = i;
break;
}
else {
fv[i] = 0;
argv[i] = AsString(v, tmp);
}
}
}
/* End code copied from Merge() */
/* All this to avoid a call to Tcl_Merge() and the corresponding call
to Tcl_SplitList() inside Tcl_Eval()... It can save a bundle! */
if (Py_VerboseFlag >= 2) {
for (i = 0; i < argc; i++)
fprintf(stderr, "%s ", argv[i]);
}
if (argc < 1 ||
!Tcl_GetCommandInfo(interp, argv[0], &info) ||
info.proc == NULL)
{
char *cmd;
if (Py_VerboseFlag >= 2)
fprintf(stderr, "... use TclEval ");
cmd = Tcl_Merge(argc, argv);
i = Tcl_Eval(interp, cmd);
ckfree(cmd); ckfree(cmd);
}
else {
Tcl_ResetResult(interp);
i = (*info.proc)(info.clientData, interp, argc, argv);
}
if (i == TCL_ERROR) {
if (Py_VerboseFlag >= 2)
fprintf(stderr, "... error: '%s'\n",
interp->result);
Tkinter_Error(self);
}
else {
if (Py_VerboseFlag >= 2)
fprintf(stderr, "-> '%s'\n", interp->result);
res = PyString_FromString(interp->result);
}
/* Copied from Merge() again */
finally:
for (i = 0; i < argc; i++)
if (fv[i]) {
ckfree(argv[i]);
}
if (argv != argvStore)
ckfree(FREECAST argv);
if (fv != fvStore)
ckfree(FREECAST fv);
Py_DECREF(tmp);
return res; return res;
} }
@ -419,6 +514,12 @@ Tkapp_GlobalCall(self, args)
PyObject *self; PyObject *self;
PyObject *args; PyObject *args;
{ {
/* Could do the same here as for Tkapp_Call(), but this is not used
much, so I can't be bothered. Unfortunately Tcl doesn't export a
way for the user to do what all its Global* variants do (save and
reset the scope pointer, call the local version, restore the saved
scope pointer). */
char *cmd = Merge(args); char *cmd = Merge(args);
PyObject *res = NULL; PyObject *res = NULL;