ASP.NET does it, so why not you? There are two reasons restarting an AppDomain is useful for a reliable .NET server program. First, the only way to change the assemblies loaded into your AppDomain is to restart it. This allows you to upgrade a program quickly without taking it completely down. Though you should probably have the inputs buffered somehow to deal with your program being offline for a split second. Second, when your program inevitably breaks, it is useful to simply restart the program rather than fail. If you design your program so major subsystems are in their own AppDomain, then you can have a master program that monitors their health and restart those that fail. Erlang does this for their processes.
The way to do this is to use .NET Remoting. The entry point into your subsystems will likely be a class factory that inherits from MarshalByRefObject. Then you write a master program that creates a new AppDomain (AppDomain.CreateDomain) and accesses the factory class (AppDomain.CreateInstanceFromandUnwrap). When the AppDomain needs to be restarted, unload it (AppDomain.Unload) and reload it. Remote method calls across the AppDomain barrier are very fast. The hard part is designing a program to use this technique effectively.
Rico Mariani gave a talk based on a paper on .NET performance. It’s mostly what you’d expect from a managed runtime. Over here someone took the time to detail the performance of most IL instructions for .NET 1. The exact perf numbers don’t matter so much as the magnitude. I wonder if lambdas in C# 3.0 are still as expensive as delegates (8X more than an instance call). I do wish someone would explain when the JIT will inline code. This could go a long way towards writing more efficient libraries. The thing about performance is that it’s more than merely learning to use a language/runtime. It’s also about conforming to the style and expectations of the language/runtime implementors.
I reported earlier that the N-Queens problem on F# was too slow. I translated the benchmark in C# and it ran 2X faster than F#. It was essentially the same program… lots of recursion and list processing. So how can F# be 2X slower than C# for the same code? One simple instruction: isinst. I learned long ago that type checking on the CLR is too slow for dynamic languages. Though F# is statically typed, it still performs type checking on discriminated union types. Specifically, operations on lists must always check if the list is null. F# does this by checking if the object is a Nil class: “obj is Nil” in C#. Type checking is extremely slow and should be avoided whenever possible in hotspots. List processing is definitely a hot spot.
When I rewrote the N-Queens problem I used my own list class which used null to represent Nil. Checking “obj == null” is extremely fast. I then modified my list to use a Nil class and performance dropped down to F# speed. The solution I used with my Scheme compiler was to use null to represent the end of the list. An alternative is skip the nil check and wrap the list access in a try-catch. It only fails in the exceptional situation anyway, since properly written code will check for nil before it calls hd. This should speed up list performance dramatically.
I’m writing a bunch of Scheme benchmarks in F#. The N-Queens problem is determining how to place 8 queens on an 8×8 chess board so all are safe. Computing all 92 possibilities does a fair amount of recursive function calls and list processing. To do this 2000 times with F# takes about 17 seconds. For comparison, it takes 26 seconds with Scheme48 (a Scheme interpreter). Compiled implementations of Scheme can solve this problem in 2 to 3 seconds. Why is F# only 2X faster than Scheme48? I’ll report the rest of the benchmarks later.
A language that properly supports modularity must deal with name collisions. In C# 3.0, extension methods allow us to dynamically add new methods to another class. So what happens when methods collide? If I add the same method signature (name + parameters) to a class, what will the compiler do?
- If I include two different namespaces, and both add the same method, the compiler warns that my method call is ambiguous. That’s good. But how can I explicitly specify which method I want?
- If the method exists in another namespace and I add the same method, the compiler does not report that I’ve modified an existing method. This means my program could change if anyone working in the same namespace accidently modifies another method. The compiler should warn that I’ve changed an existing method.
This post explains how to look at the optimized x86 generated by the CLR.
F# is moving out of research into a first-class language running on .NET. F# is a derivative of OCaml, a strongly-typed functional language with imperative and OO features. I’ve had the great fortune of working with Don Syme on Project 7 (a largely failed attempt to port “academic” languages to .NET) and at MSR Cambridge a long time ago. He’s a very sharp guy who also contributed to the design and implementation of generics in C# and the CLR. Who says nothing ever comes from research groups?
There are times when it is more appropriate to send a chunk of code to another machine rather than receive a large amount of data. One would think this would be more practical with portable languages running on the CLR or JVM. One should be able to serialize a method, then regenerate the method on another machine. Fortunately, this post describes how to generate a DynamicMethod from a MethodInfo in .NET. The next step is to serialize the MethodInfo instance, which looks a bit tricky. I’m still looking into that.
It should be possible to write a language interpreter on top of Java or .NET and rely on method inlining to instantly get compiled code. Implement most of your interpreter opcodes as small static methods: Add, Subtract, LessThan, etc. Translate your language into Java/C# methods that call these opcodes. When the JIT sees all these small static methods, it should inline and optimize them. Presto! You’ve just written a compiler. It’s a bit like using partial evaluation to generate a compiler from an interpreter. I think Peter Sestof did this the hard way a long time ago.