Game Development Community

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:
// 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


#1
11/26/2012 (5:07 am)
FWIW here are the versions I am using for my Python module with some simple speedups:
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.