Friday, September 01, 2006

spidermonkey-dotnet 0.1.1

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()

3 Comments:

Blogger Mister Fingers said...

This is pretty cool stuff, I did a Delphi binding for spidermonkey a long time ago. If I can be of any help let me know.

I'm just wondering why you are doing it though? Learning pinvoke? Why not use Microsoft.JScript?

Just curious, good work!

9/05/2006 3:34 PM  
Blogger abstraktmethodz said...

I originally had an idea for a program that gave the users access to a server-side interpreter. I had a lot of success with SpiderMonkey and C++, but wanted to move to .Net because I find coding with it much faster.

Now the only option for embedding script provided by Microsoft is the VSA engine. This API would force the user to write an entire class, that would basically be just compiled on the fly. Have to implement a whole interface was way off from what I needed, plus JIT'ing lots of small scripts seemed like a lot of overhead. And then what about the security of these scripts? I still can't find a real .Net interpreter.

But your also right, P/Invoke and SpiderMonkey are both too cool to ignore.

Did you have a good test bed for your Delphi binding? My core binding exposes a lot of API and I haven't verified they all work well. Otherwise I should have source control working over on codeplex.com soon if you'd like to help out.

9/06/2006 9:52 AM  
Anonymous Anonymous said...

It's pretty cool for your working

10/15/2006 6:12 AM  

Post a Comment

Links to this post:

Create a Link

<< Home