Looking into the performance of spidermonkey-dotnet, I decided to establish a baseline. Executing JavaScript calls to purely indigenous JavaScript functions and objects should perform as well as doing the same thing from an unmanaged program. I did exactly that, testing my own shell against Mozilla’s jsshell. The performance now matches a native-code only binary, after a fix to the problem below. The latest version of the project is available at http://www.codeplex.com/Wiki/View.aspx?ProjectName=spidermonkeydotnet. Following is a description of the bug, and how to avoid it.
The binding has several .Net delegates available that appear like this one:
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate Boolean JS_PropertyDelegate(IntPtr cx,
IntPtr obj, Int32 id, IntPtr vp);
Objects are embedded in the engine by passing delegates instead of function pointers to the SpiderMonkey API. Whenever a delegate is called it incurs the overhead of interop, so when possible this needs to be avoided.
Here is another snip from the binding:
[DllImport("js32.dll")]
public static extern Boolean JS_PropertyStub(IntPtr cx,
IntPtr obj, Int32 id, IntPtr vp);
JS_PropertyStub is the corresponding SpiderMonkey API function, that is used to define a class that does not require a custom handler. I had been passing this function pointer to the engine in the form of the delegate above.
new JS.JS_PropertyDelegate(JS.JS_PropertyStub)
The stubs are actually called by the API. In the case of the global object with all its callbacks set to stubs this is very often. Using the code above each of those calls causes marshalling overhead. This is because those delegates are not marshalled as function pointers in unmanaged memory (where the functions reside). The CLR is indeed marshalling all the parameters to the stub function into managed memory, and then back into unmanaged memory when it decides to call the real pointer. I realize this is a special case and I was a little too optimistic when coding this, but it would sure be nice if the marshalling smartened up just enough to pass in that raw pointer.
Now, in the new version of the binding, when passing stubs please acquire them as an IntPtrs using these functions:
public static IntPtr JS_GetPropertyStub()
public static IntPtr JS_GetEnumerateStub()
public static IntPtr JS_GetResolveStub()
public static IntPtr JS_GetConvertStub()
public static IntPtr JS_GetFinalizeStub()