Speeding up the engine by avoiding name lookups: Is this possible?
by Demolishun · in Torque 3D Professional · 11/25/2012 (9:29 pm) · 1 replies
First here is my code:
For reference this is SWIG code and contains a mixture of C++ at the top and Python at the bottom. The Python code is calling the above C++ code and instantiating the object (class). The Call function on the C++ object is nearly the same as the call in Con::execute in console.cpp. The main differences are that I don't create a temporary namespace object as it is part of the class itself, I don't try to call console functions from other threads so there no code in the else part of the if else statement, and I am dynamically allocating space so I can avoid allocating that space from within Python. The last difference keeps me from having to make extra calls as well. Which is the main purpose behind rewriting this code is to reduce function calls and lookups.
I am trying to figure out if avoiding a name lookup will be a detriment to my Python interface. One thing I have found is that when running this code I can create functions like this:
The first call to "test" gives me the "Unknown command" error message as it is not defined yet. The second call prints out "test code". The third test prints out "new test code". What messes with my head in this is that the third call is using the previously looked up namespace object. That means you can redefine a function in the console and it will still find the correct function without doing an explicit lookup. Apparently the previously referenced object is still valid and has been updated.
After seeing this I thought: Can't we speed up the engine somehow by avoiding this lookup after the initial lookup?
If yes, then I think the console could be sped up immensely because from what I understand it spends a lot of time looking up functions.
Edit:
Okay, I followed the rabbit hole a little further. It looks like I am just avoiding the namespace lookup, not the actual function lookup which occurs a little bit later in the calling chain for console functions. So not avoiding all the lookup, just part of them. This might be a pretty good small improvement in speed for my purposes, but definitely not avoiding the function name lookup.
// object to hold function calls for methods
%{
extern ExprEvalState gEvalState;
extern StringStack STR;
class FunctionCaller{
private:
const char *fname;
Namespace::Entry *ent;
public:
FunctionCaller(const char *name);
const char *Call(int argc, const char **argv);
};
FunctionCaller::FunctionCaller(const char *name){
fname = name;
ent = NULL;
}
const char *FunctionCaller::Call(int argc, const char **argv){
#ifdef TORQUE_MULTITHREAD
if(Con::isMainThread())
{
#endif
//Namespace::Entry *ent;
if(!ent){
StringTableEntry funcName = StringTable->insert(fname);
ent = Namespace::global()->lookup(funcName);
}
if(!ent)
{
// consider putting exception here
Con::warnf(ConsoleLogEntry::Script, "%s: Unknown command.", fname);
// Clean up arg buffers, if any.
STR.clearFunctionOffset();
return "";
}
// create targs list including function name
const char** targs = (const char**)malloc(sizeof(char*)*(argc+1));
targs[0] = fname;
memcpy((void*)&targs[1],(void*)&argv[0],sizeof(char*)*(argc));
// call the function
const char *result = ent->execute(argc+1, targs, &gEvalState);
// free the argument list
free(targs);
// return the result
return result;
#ifdef TORQUE_MULTITHREAD
}
else
{
// some type of exception here
return "";
}
#endif
}
%}
class FunctionCaller{
public:
FunctionCaller(const char *name);
const char *Call(int argc, const char **argv);
};
// native python code
%pythoncode %{
class __Con(object):
def __getattr__(self, name):
# lookup function and return callable object
print "Con::Assigning attribute:",name
# this will only happen once, after that the name is an attribute on the object
fcObject = FunctionCaller(name)
setattr(self, name, lambda *args: fcObject.Call(len(args) or 0, map(str,args)))
return getattr(self, name)
Con = __Con()
%}For reference this is SWIG code and contains a mixture of C++ at the top and Python at the bottom. The Python code is calling the above C++ code and instantiating the object (class). The Call function on the C++ object is nearly the same as the call in Con::execute in console.cpp. The main differences are that I don't create a temporary namespace object as it is part of the class itself, I don't try to call console functions from other threads so there no code in the else part of the if else statement, and I am dynamically allocating space so I can avoid allocating that space from within Python. The last difference keeps me from having to make extra calls as well. Which is the main purpose behind rewriting this code is to reduce function calls and lookups.
I am trying to figure out if avoiding a name lookup will be a detriment to my Python interface. One thing I have found is that when running this code I can create functions like this:
Con.test()
engine.evaluate('function test(){echo("test code");}')
Con.test()
engine.evaluate('function test(){echo("new test code");}')
Con.test()The first call to "test" gives me the "Unknown command" error message as it is not defined yet. The second call prints out "test code". The third test prints out "new test code". What messes with my head in this is that the third call is using the previously looked up namespace object. That means you can redefine a function in the console and it will still find the correct function without doing an explicit lookup. Apparently the previously referenced object is still valid and has been updated.
After seeing this I thought: Can't we speed up the engine somehow by avoiding this lookup after the initial lookup?
If yes, then I think the console could be sped up immensely because from what I understand it spends a lot of time looking up functions.
Edit:
Okay, I followed the rabbit hole a little further. It looks like I am just avoiding the namespace lookup, not the actual function lookup which occurs a little bit later in the calling chain for console functions. So not avoiding all the lookup, just part of them. This might be a pretty good small improvement in speed for my purposes, but definitely not avoiding the function name lookup.
About the author
I love programming, I love programming things that go click, whirr, boom. For organized T3D Links visit: http://demolishun.com/?page_id=67
Torque Owner Demolishun
DemolishunConsulting Rocks!
extern ExprEvalState gEvalState; extern StringStack STR; class FunctionCaller{ private: const char *fname; SimObject *sobject; Namespace::Entry *ent; char idBuf[32]; public: FunctionCaller(const char *name); FunctionCaller(SimObject *object, const char *name); ~FunctionCaller(); const char *Call(int argc, const char **argv); const char *ObjectCall(int argc, const char **argv); }; FunctionCaller::FunctionCaller(const char *name){ sobject = NULL; fname = name; ent = NULL; } FunctionCaller::FunctionCaller(SimObject *object, const char *name){ sobject = object; if(sobject){ sobject->registerReference(&sobject); } fname = name; ent = NULL; } FunctionCaller::~FunctionCaller(){ if(sobject){ sobject->unregisterReference(&sobject); } } const char *FunctionCaller::Call(int argc, const char **argv){ #ifdef TORQUE_MULTITHREAD if(Con::isMainThread()) { #endif //Namespace::Entry *ent; if(!ent){ StringTableEntry funcName = StringTable->insert(fname); ent = Namespace::global()->lookup(funcName); } if(!ent) { // consider putting exception here Con::warnf("FunctionCaller::Call %s: Unknown command.", fname); // Clean up arg buffers, if any. STR.clearFunctionOffset(); return ""; } // create targs list including function name const char** targs = (const char**)malloc(sizeof(char*)*(argc+1)); targs[0] = fname; memcpy((void*)&targs[1],(void*)&argv[0],sizeof(char*)*(argc)); // call the function const char *result = ent->execute(argc+1, targs, &gEvalState); // free the argument list free(targs); // return the result return result; #ifdef TORQUE_MULTITHREAD } else { // some type of exception here Con::warnf("FunctionCaller::Call %s: Called outside main thread.", fname); return ""; } #endif } const char *FunctionCaller::ObjectCall(int argc, const char **argv){ if(!sobject){ Con::warnf("FunctionCaller::ObjectCall object appears to no longer exist."); return ""; } if(sobject->getNamespace()) { if(!ent){ StringTableEntry funcName = StringTable->insert(fname); ent = sobject->getNamespace()->lookup(funcName); dSprintf(idBuf, sizeof(idBuf), "%d", sobject->getId()); } if(!ent) { STR.clearFunctionOffset(); return ""; } // add 2 more slots to the buffer // create targs list including function name const char** targs = (const char**)malloc(sizeof(char*)*(argc+2)); targs[0] = fname; targs[1] = NULL; memcpy((void*)&targs[2],(void*)&argv[0],sizeof(char*)*(argc)); // stick object id in args targs[1] = idBuf; // call the function SimObject *save = gEvalState.thisObject; gEvalState.thisObject = sobject; const char *ret = ent->execute(argc+2, targs, &gEvalState); gEvalState.thisObject = save; // free the argument list free(targs); return ret; } Con::warnf("FunctionCaller::ObjectCall %d has no namespace: %s", sobject->getId(), fname); return ""; }I am not sure if this will help us make the existing console function calls faster. For external calls that don't rely on the consoles name resolution to determine that it needs to be called it just might.