Posted on March 16, 2008 00:34 by jpager

Two days ago, I posted an example of a way to apply IronPython scripting in .NET. At the very end of my post, I wondered about the possibility of compiling the script as a .NET assembly.

I'm happy to say, I have found a way to implement this functionality (and I might mention at this point that this is my last post about IronPython). First, I'll explain how I was able to do it, and then I'll explore whether we really gain anything from having a pre-compiled script.

First, compiling the script. The idea is that we only want to recompile our script if (a) the DLL does not exist or (b) our script is more recent than the DLL (i.e., it has been edited). We'll examine our files using the System.IO.FileInfo class. If we need to compile the script, we'll use IronPython's implementation of System.CodeDom.Compiler.CodeDomProvider, which will allow us to compile a python script into MSIL, and save the assembly as a DLL (this is located in the IronPython.CodeDom namespace):

FileInfo fi_in = new FileInfo(exportScript);
FileInfo fi_out = new FileInfo("BatchExport.dll");
if (!fi_out.Exists || (fi_in.LastWriteTimeUtc >= fi_out.LastWriteTimeUtc))
{
   PythonProvider p = new PythonProvider();
   CompilerParameters _params = new CompilerParameters(new string[] { }, "BatchExport.dll");
   CompilerResults r = p.CompileAssemblyFromFile(_params, new string[] { exportScript });
   if (r.Errors.Count > 0) throw new Exception("Compiler error.");
}


This is fairly straightforward. We create a compiler, call CompileAssemblyFromFile(), which is configured to save the assembly as BatchExport.dll and compile the file containing our python script. The compilation takes a short amount of time, so we only want to compile if absolutely necessary.

The next step is actually calling into the compiled assembly. As I mentioned last time, calling a Python assembly from C# is non-trivial. That python assembly contains dynamic types, which are not easily reflected into useful C# objects. So my approach was to create a PythonEngine, import the assembly into said PythonEngine, and call the method using Python.

To do this, I first altered my script so that my Export() method is contained in a class called BatchExport, rather than being a global method. Then, in my C# code, I created an instance of the BatchExport class, and called the Export() method on that object.

using (PythonEngine engine = new PythonEngine())
{
   engine.AddToPath(AppDomain.CurrentDomain.BaseDirectory);
   engine.Import("clr");
   engine.Execute("clr.AddReference('BatchExport.dll')");
   engine.Execute("import BatchExport");
   engine.Execute("be = BatchExport()");
   List<string> _params = new List<string>();
   _params.Add("order");
   ExportDelegate exMethod = engine.CreateMethod<ExportDelegate>("return be.Export(order)", _params);
   bool res = exMethod(order);
}


Our first step was to actually load our assembly into our python engine. To do that, we call clr.AddReference(). This allows us to include any .NET assembly in our script.

Then, we instantiated our BatchExport object, and use the CreateMethod() method to actually call the object's Export() method. Now the compile-script-only-if-it-has-changed functionality works perfectly.

Of course, after doing this, I made a big mistake--I decided to see how much time this actually buys us. So I created a simple script that processes a batch 500 times using the old method (that simply calls ExecuteFile()) and the same batch 500 times using the new method (I also created a new Export() python script that doesn't write to any files, in order to avoid any bias involving file sizes). The result was at least a little surprising--it took the method that pre-compiles the script 15 times as long to process the batches as it does the method that executes the script anew each time.

Possible culprits? Probably the cost of reflection is the main one. I don't think the check on the file times could make that big a difference. Clearly this little operation is quite expensive:

engine.Import("clr");
engine.Execute("clr.AddReference('BatchExport.dll')");
engine.Execute("import BatchExport");

So, I guess what we can take out of this is that compiling python scripts to .NET DLLs comes at a considerable cost, both in performance and complexity, at least if the script is simple. It may be cheaper to precompile the assembly if our script is hundreds (or thousands) of lines long, but for fairly basic scripts, you might as well just leave them as scripts.



Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Related posts

Add comment


 

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

November 20. 2008 19:48

|