Every LOB application within .NET seems to consist of two things: 100000000000000000 if( x != null) { ... }
and 32456783 SomeEnum.ToString()
executions. Lets leave the if-case
and instead focus on the 400x slowerSomeEnum.ToString()
. Yes. You read “400x slower”, but slower than what? And does it matter?
Today, I overheard discussions about Enums
and ToString
and performance considerations of using it. I know what you are thinking right now. You are thinking about Donald Knuths quote: “Premature optimization is the root of all evil”. But I had to know. Was this true? So as soon as I got home I had to try. Yes it seems to be true. And if SomeEnum.ToString()
is heavily used as part of your “algorithm”, maybe you should re-consider changing your design, as it might be rather simple.
Edited
Sweet! Didn’t see this one coming. Not saying mindlessly switch. Follow up.
Measurements
I measured the average milliseconds of five iterations of accessing three values 10000 times. Warm up behavior by buring one cycle was applied, just in case that it caches something or …
Enum.ToString()
=> ~16msStaticClass.Value:string
=> ~0.04ms => 400x faster
enum Procedures1
{
One,
Two,
Three
}
VS
static class Procedures2
{
public const string One = "One";
public const string Two = "Two";
public const string Three = "Three";
}
Now, you can argue that the enum can also represent two values like a Tuple
. You could accomplish this by letting the static class hold values of e.g. struct
with two fields, one for the numeric value and one for the string value. Tried this as well.
Enum.ToString()
=> ~16msStaticClass.Value:struct
=> ~0.04ms => 400x faster
static class Procedures3
{
public static readonly Procedure One = new Procedure(1, "One");
public static readonly Procedure Two = new Procedure(2, "Two");
public static readonly Procedure Three = new Procedure(3, "Three");
}
struct Procedure
{
public readonly int Id;
public readonly string Name;
public Procedure(int id, string name)
{
Id = id;
Name = name;
}
}
Someone might think, well, that’s not fare, there’s a method call in there. OK, fair enough, lets measure a slightly updated test-case, where the struct
has a custom ToString
.
public override string ToString()
{
return Name;
}
work.Value = Procedures3.One.ToString();
work.Value = Procedures3.Two.ToString();
work.Value = Procedures3.Three.ToString();
Enum.ToString()
=> ~16msStaticClass.Value:struct.ToString()
=> ~0.04ms => 400x faster
Memory usage
Of course, performance is not only about time taken, but also the memory usage. Looking at the peak value of the memory usage for the process:
Enum.ToString()
=> ~12mbStaticClass.Value
=> ~8mb
Summary
Not much to say, but please feel free to comment on stuff that I might have overlooked.
//Daniel
Sample
A simple console application, compiled with Release
configuration, .NET 4.5.
class Program
{
private const int NumberOfMeasurements = 5;
private const int Burn = 1;
static void Main(string[] args)
{
var work = new Work();
var totals = new List<double>(NumberOfMeasurements + Burn);
var sw = new Stopwatch();
for (var c = 0; c < totals.Capacity; c++)
{
sw.Start();
for (var i = 0; i < 10000; i++)
{
//Test case 1 => aprox 16ms => 400x slower
work.Value = Procedures1.One.ToString();
work.Value = Procedures1.Two.ToString();
work.Value = Procedures1.Three.ToString();
//Test case 2 => aprox 0.04ms
//work.Value = Procedures2.One;
//work.Value = Procedures2.Two;
//work.Value = Procedures2.Three;
//Test case 3 => aprox 0.04ms
//work.Value = Procedures3.One.Name;
//work.Value = Procedures3.Two.Name;
//work.Value = Procedures3.Three.Name;
}
sw.Stop();
totals.Add(sw.Elapsed.TotalMilliseconds);
sw.Reset();
}
Console.WriteLine(totals.Skip(Burn).Average());
var process = Process.GetCurrentProcess();
Console.WriteLine("Peak physical memory usage: "
+ process.PeakWorkingSet64 / 1000000);
}
}
public class Work
{
public string Value { get; set; }
}
enum Procedures1
{
One,
Two,
Three
}
static class Procedures2
{
public const string One = "One";
public const string Two = "Two";
public const string Three = "Three";
}
static class Procedures3
{
public static readonly Procedure One = new Procedure(1, "One");
public static readonly Procedure Two = new Procedure(2, "Two");
public static readonly Procedure Three = new Procedure(3, "Three");
}
struct Procedure
{
public readonly int Id;
public readonly string Name;
public Procedure(int id, string name)
{
Id = id;
Name = name;
}
}