Relative Speed: F# v C# v VB.NET
October 1, 2013 2 Comments
(Spoiler alert: F# is faster – by alot)
As part of my presentation on F# which I hope to show at CodeCamp 2013, I did a quick speed test among C#, VB.NET, and F#. I thought about Dr. Evil and how he tried to ransom the UN for “One Million Dollars” and I thought about how the Federal Reserve is creating money from nothing to the tune of $40 Million a month via QE3 – so I built a quick app that does the exact same thing as the Federal Reserve does (except mine has unit tests, theirs probably does not).
I created a class for a Dollar bill (C#, VB, F#):
- public class Dollar
- {
- public Int32 Id { get; set; }
- public String SerialNumber { get; set; }
- public Int32 FederalReserveDistrict { get; set; }
- public Int32 SeriesDate { get; set; }
- public String Signature { get; set; }
- }
- Public Class Dollar
- Public Property Id As Int32
- Public Property SerialNumber As String
- Public Property FederalReserveDistrictNumber As Int32
- Public Property SeriesDate As Int32
- Public Property Signature As String
- End Class
- type dollar =
- {Id: int;
- SerialNumber: string;
- federalReserveDistrict: int;
- seriesDate: int;
- signature: string}
I then implemented the same algorithm that the Federal reserve uses (C#, VB, F#):
- public List<Dollar> GetDollars(Int32 numberOfDollars)
- {
- List<Dollar> dollars = new List<Dollar>();
- Dollar currentDollar = null;
- Random random = new Random();
- for (int i = 0; i < numberOfDollars; i++)
- {
- currentDollar = new Dollar();
- currentDollar.FederalReserveDistrict = random.Next(1, 13);
- currentDollar.Id = i;
- String serialNumber = String.Empty;
- for (int j = 0; j < 9; j++)
- {
- serialNumber += random.Next(0, 9).ToString();
- }
- currentDollar.SerialNumber = serialNumber;
- currentDollar.SeriesDate = 2000 + random.Next(0, 10);
- dollars.Add(currentDollar);
- }
- return dollars;
- }
- Public Function GetDollars(ByVal numberOfDollars As Int32) As List(Of Dollar)
- Dim dollars As New List(Of Dollar)()
- Dim currentDollar As Dollar = Nothing
- Dim random As New Random()
- For dollarIndex As Integer = 0 To numberOfDollars – 1
- currentDollar = New Dollar()
- currentDollar.FederalReserveDistrictNumber = random.Next(1, 13)
- currentDollar.Id = dollarIndex
- Dim serialNumer As String = String.Empty
- For serialNumberIndex As Integer = 0 To 9
- serialNumer += random.Next(0, 9).ToString()
- Next
- currentDollar.SerialNumber = serialNumer
- currentDollar.SeriesDate = 2000 + random.Next(0, 10)
- dollars.Add(currentDollar)
- Next
- Return dollars
- End Function
- type dollarProvider() =
- let randomNumberGenerator = new System.Random()
- let createSerialNumber =
- List.init 9 (fun _ -> randomNumberGenerator.Next(0,9))
- |> Seq.map string
- |> String.concat ""
- let createDollar id =
- let returnDollar = {Id=id;
- SerialNumber= createSerialNumber ;
- federalReserveDistrict=randomNumberGenerator.Next(0,13);
- seriesDate=2000+randomNumberGenerator.Next(0,9);
- signature=""}
- returnDollar
- member this.GetDollars (numberOfDollars:int) =
- List.init numberOfDollars (fun index -> createDollar index)
I then created a WPF I with the following XAML:
- <Grid>
- <Button Content="Run C#" Height="32" HorizontalAlignment="Left" Margin="12,97,0,0" Name="CSharpButton" VerticalAlignment="Top" Width="95" Click="CSharpButton_Click" />
- <TextBox Height="20" HorizontalAlignment="Left" Margin="12,150,0,0" Name="CSharpResultsTextBox" VerticalAlignment="Top" Width="95" />
- <Button Content="Run VB" Height="29" HorizontalAlignment="Left" Margin="120,99,0,0" Name="RunVBButton" VerticalAlignment="Top" Width="99" Click="RunVBButton_Click" />
- <TextBox Height="19" HorizontalAlignment="Left" Margin="117,150,0,0" Name="VBResultsTextBox" VerticalAlignment="Top" Width="102" />
- <Button Content="Run F#" Height="28" HorizontalAlignment="Right" Margin="0,99,162,0" Name="RunFSharpButton" VerticalAlignment="Top" Width="105" Click="RunFSharpButton_Click" />
- <TextBox Height="22" HorizontalAlignment="Left" Margin="239,147,0,0" Name="FSharpResultsTextBox" VerticalAlignment="Top" Width="102" />
- <Label Content="Number Of Dollars" Height="30" HorizontalAlignment="Left" Margin="12,18,0,0" Name="label1" VerticalAlignment="Top" Width="115" />
- <TextBox Height="24" HorizontalAlignment="Left" Margin="126,22,0,0" Name="DollarsTextBox" VerticalAlignment="Top" Width="115" Text="1000000" />
- </Grid>
and Code Behind
- private void CSharpButton_Click(object sender, RoutedEventArgs e)
- {
- Stopwatch stopWatch = new Stopwatch();
- stopWatch.Start();
- CS.DollarProvider provider = new CS.DollarProvider();
- Int32 numberOfDollars = Int32.Parse(this.DollarsTextBox.Text);
- provider.GetDollars(numberOfDollars);
- stopWatch.Stop();
- this.CSharpResultsTextBox.Text = stopWatch.Elapsed.TotalSeconds.ToString() + " seconds";
- }
- private void RunVBButton_Click(object sender, RoutedEventArgs e)
- {
- Stopwatch stopWatch = new Stopwatch();
- stopWatch.Start();
- VB.DollarProvider provider = new VB.DollarProvider();
- Int32 numberOfDollars = Int32.Parse(this.DollarsTextBox.Text);
- provider.GetDollars(numberOfDollars);
- stopWatch.Stop();
- this.VBResultsTextBox.Text = stopWatch.Elapsed.TotalSeconds.ToString() + " seconds";
- }
- private void RunFSharpButton_Click(object sender, RoutedEventArgs e)
- {
- Stopwatch stopWatch = new Stopwatch();
- stopWatch.Start();
- FS.dollarProvider provider = new FS.dollarProvider();
- Int32 numberOfDollars = Int32.Parse(this.DollarsTextBox.Text);
- provider.GetDollars(numberOfDollars);
- stopWatch.Stop();
- this.FSharpResultsTextBox.Text = stopWatch.Elapsed.TotalSeconds.ToString() + " seconds";
- }
And the results are pretty revealing:
I acknowledge that you can jerry rig C# to improve performance – esp using Pointers and the GOTO statement. This exercise was to show performance doing the commonly-accepted way to solve a problem in each language (without FxCOP yelling at you). It was not designed to see how clever you can get with C# to match the performance of the F# implementation. As Rob Seder said “You can also bang in a screw by turning around the screwdriver and whacking away.” If you need speed, F# is the best tool for the job.
F# example is not comparable to C#.
createSerialNumber will never get called in this test.
if you carrect it and make a true function, F# example performance would get about 50% slower than C#.
I tried the VB code and it gave me the same result of c# 3.45 seconds.