danielwertheim

danielwertheim


notes from a passionate developer

Share


Sections


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

C# - SomeEnum.ToString measured to be 400x slower

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 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.

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();

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:

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 Releaseconfiguration, .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;
  }
}
View Comments