Welcome!

.NET Authors: Liz McMillan, Peter Silva, Yakov Werde, Matthew Pollicove , Corey Roth

Related Topics: .NET

.NET: Article

F# on a Virtual Super Computer

Understanding grid computing

Listing 5 defines a module called pb and creates a prototype for the CalculatePiDigits function. To a C# developer this looks really strange! It's essentially saying CalculatePiDigits takes an integer as an input and returns a string. The strangeness will continue as we look at the meat of the calculation class. But before I got into full-fledged F# development, I wanted to make sure it would run on the grid. I added the code in Listing 6 to pb.fs as a test.

From Listing 6, we see that we begin by declaring the implementation of module pb followed by opening System. Opening in F# is equivalent to the using statement in C#. Next we define a function CalculatePiDigits using the let syntax. CalculatePiDigits will explicitly take an integer, n, as an input argument and implicitly return a string. This is a key and unique feature of F#, the ability to mix explicit and implicit typing.

To call the F# code contained in the pb module, I modified the Start method of the PiCalcGridThread class and included the Microsoft.FSharp namespace statement in Listing 7.

After compiling the solution and running it, everything appeared to work but it showed Pi as 3 with no numbers after the decimal. A little digging into the Alchemi user guide and the log file revealed that my F# based plouffe_bellard.dll wasn't being sent to the executor. To overcome that I had to add the lines in Listing 8 to the startup code.

Adding file dependencies to the GApplication manifest let Alchemi distribute these files with the tasks. As a result the F# code can run on remote machines. Alternatively you can specify the standalone compiler option to compile these into a single DLL. If you use this option the dependency code looks like Listing 9. After this was done, the job ran. Figure 4 shows the result of the first test run.

I could now move on to rewriting the C# Plouffe-Bellard calculation class in F#. As a result of a lot of reading and instant messaging with optionsScalper, the first working F# Plouffe-Bellard module is in Listing 10.

To compare the differences between C# and F# take a look at Listing 2 and Listing 10. As you can see the use of F# mutables helps in this endeavor immensely! Mutables in F# are modifiable, by reference, values that are implicitly dereferenced. Mutables use the local mutation assignment operator: id exp r. As you become a more experienced F# developer, you'll find yourself using mutables less. However, mutables are indispensable when doing direct C# to F# conversion as you'll see.

Going All In
With the basic Plouffe-Bellard algorithm working well in F#, it was time to get rid of the C# all together. I began by converting the C# GThread over to F# by adding the definition to the pb.fsi file and the implementation to pb.fs as shown in Listing 11 and 12.

You'll see the similarities between Listing 4 and Listing 12. The differences include:

  • Inheritance technique
  • Constructor implementation
  • Use of base class
  • Method overloading technique
While certainly not an all-inclusive list of differences, they are some of the items I ran into. Inheritance is done using the inherit keyword just below the class definition.

Constructors are implemented using the new keyword and can be defined as using the keyword to access the base class. The use of the base class is required to implement method overloading using the override keyword.

Finally, I implemented the main code in F# in Listing 13.

In this code segment, some of the issues I ran into with F# are:

  • Hooking into events
  • Exception handling
  • Type up and down casting
The method for hooking events in F# reminds me of COM connection points. Instead of the addition assignment operator used in C#, we use the Add method of the IEvent interface. To the Add method we pass fun evArgs -> expr, where expr is an appropriate implementation of a callback. Exception handling takes on the try/with and try/finally forms in F#. Try/with is similar to try-catch blocks in C# while try/finally is similar to the try-finally. Up casting occurs when a type is converted to a more general type whereas down casting can occur on types that are derived from a more general type.

An example would be type car is derived from type vehicle then car needs to be up cast to a vehicle and vehicle needs to be down cast to a car.

More Advanced F#
F# was originally written by Dr. Don Syme of Microsoft Research. Dr. Syme still maintains F# and plays an active part of the F# community. After discussing this article and its code with him, he suggested some changes. The changes highlight some of the advantages of using F#. In particular he suggested:

  • Use for-loops instead of while loops
  • Try to remove mutables
  • Try to stick with F# convention and use lower case variable names for most letbound variables and arguments
  • Make your classes smaller and more reusable. Classes rarely need to capture variables explicitly
Dr. Syme was nice enough to provide a rewrite of some of my code segments. In Listing 14 you can see some of the major areas where thinking in F# can produce more elegant code.

The hardest thing for me to adapt to has been the use of let-bound functions. While it reminds me of the use of macros in other languages, I'm still not used to seeing them inline with the rest of the code. You can see an example of this in Listing 14. The declaration of check in the is_prime function took me a while to understand.

Performance Comparison
One of the most interesting parts of this process was examining the performance differences between C# and F#. My initial assumption was that the F# code had to be slower. This, in fact, is not the case. In a starting benchmark, I wanted to examine the differences in performance of the Plouffe-Bellard calculation.

Without any optimization the F# implementation took 453 ms. C# finished up in 62 ms. Turning optimization on caused the C# results to drop to 46 ms. And, most shocking of all, the F# was dead even at 46 ms! Apparently, the F# compiler does an excellent job optimizing the code. Continuing on, I ran the entire C# and F# grid applications against one another. Without any optimization the C# code took 3.9 seconds to calculate 100 digits of pi while F# only took 2.8 seconds. Optimizing this application showed that F# would finish in 1.5 seconds while C# would not complete any faster than 2.6 seconds. (Table 1)

A look at the IL showed the reason for the difference. From the beginning of Listing 10 you can see the use of the inline keyword, not available in C#, this lets F# squeeze out a little more performance. If you're not familiar with the concept of inline functions, let me explain. Each time a function is called arguments need to be pushed onto the stack or stored in registers. When the function is done results need to be pushed onto the stack, all this takes time. The cost of these calls can be avoided by moving the function into the body of the calling code. The C# compiler doesn't support the inlining, instead, it relies on the JIT to do this. Here's a simplified list of heuristics that the JIT uses to decide if a method should be inlined:

  • Methods that are greater than 32 bytes of IL won't be inlined.
  • Virtual functions aren't inlined.
  • Methods that have complex flow control won't be in-lined. Complex flow control is any flow control other than if/then/else; in this case, switch or while.
  • Methods that contain exception-handling blocks aren't inlined, though methods that throw exceptions are still candidates for inlining.
  • If any of the method's formal arguments are structs, the method won't be inlined.
F# can still take advantage of the JIT optimization but also allows the developer to control the use of method inlining. As a result, an F# developer can more explicitly control the performance characteristics of an application.

Conclusion
The intent of this article was to provide you with a glimpse into some exciting new technologies. I've presented some strategies for converting C# to F#, implementation of algorithms in F# and corresponding performance results. While discussing this article with a number of colleagues, it's obvious that there are a lot of applications for the coupling of these technologies. Further work might include the integration of grid computing into the F# language as well as the F# interactive console. Hopefully this article will inspire you to include F# and grid computing in your list of tools available to attack future problems.

Resources

More Stories By Chad Albrecht

Chad Albrecht is president of EnerLinx.com, Inc.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.