Tech Off Thread

13 posts

Enums are expensive in dictionaries?

Back to Forum: Tech Off
  • User profile image
    W3bbo

    I was building a cellular automaton earlier (fireflies simulation) and I decided to use a C# enum to represent a fly's current state (Charging, Sensitive, and Flashing). This is fine.

     

    However when it came to rendering the CA world I needed to map a Color instance to each state, so I created a static Dictionary<FlyState,Color> member whereby a colour could be matched to a fly's state.

     

    However NProf reports that the dictionary's FindEntry call (from the indexer property) was consuming 50% of my program's CPU time (eh wtf).

     

    I modified my code to use Dictionary<Int32,Color> and changed the indexer property call to cast the FlyState to int, and suddenly it dropped from 50% down to 4%.

     

    I'd have thought Dictionary would call .GetHashCode on the enum directly, which (should) be identical to Int32.GetHashCode.

     

    So what's going on, eh?

  • User profile image
    figuerres

    how did you declare the enum ??

     

    if you did not use the format below  try this:

     

    enum   FlyState : int {

     state =1,

    state2 = 2, ...

    };

     

    see if that " : int " makes it different.

     

     

  • User profile image
    spivonious

    What IL is the compiler outputting? That might give you some clues as to why it's slowing down.

  • User profile image
    W3bbo

    spivonious said:

    What IL is the compiler outputting? That might give you some clues as to why it's slowing down.

    I've done some reading, it turns out that Enum doesn't implement IEquatable, so the Dictionary<> class needs to perform boxing/unboxing on the enumeration instance on every comparison (and I'm doing 40,000 on every tick) which then calls the default enum comparison which also gets the names and other metadata about the enum value.

     

    See this article.

  • User profile image
    ktr

    You should use an array. Just make your enum values equal to the corresponding indices.

     

    enum FlyState {
      State1 = 0,
      State2 = 1,
      // ...
    }
    // ...
    var flyStateColors = new Color[Enum.GetValues(typeof(FlyState)).Length];
    // or
    var flyStateColors = new Color[/* hardcoded value */];
    // set a value
    flyStateColors[(int)FlyState.State1] = Color.FromArgb(255, 0, 0);
    // get a value
    var color = flyStateColors[(int)someFlyState];

     

    You could even make extension methods to get/set values of a color array based on FlyState values instead of integer indices.

  • User profile image
    figuerres

    W3bbo said:
    spivonious said:
    *snip*

    I've done some reading, it turns out that Enum doesn't implement IEquatable, so the Dictionary<> class needs to perform boxing/unboxing on the enumeration instance on every comparison (and I'm doing 40,000 on every tick) which then calls the default enum comparison which also gets the names and other metadata about the enum value.

     

    See this article.

    EEEWWW.... yuck  boxing and unboxing that much would be EVIL!!!  no wonder is goes to heck .... Expressionless

  • User profile image
    exoteric

    ktr said:

    You should use an array. Just make your enum values equal to the corresponding indices.

     

    enum FlyState {
      State1 = 0,
      State2 = 1,
      // ...
    }
    // ...
    var flyStateColors = new Color[Enum.GetValues(typeof(FlyState)).Length];
    // or
    var flyStateColors = new Color[/* hardcoded value */];
    // set a value
    flyStateColors[(int)FlyState.State1] = Color.FromArgb(255, 0, 0);
    // get a value
    var color = flyStateColors[(int)someFlyState];

     

    You could even make extension methods to get/set values of a color array based on FlyState values instead of integer indices.

    Nice, I was thinking the same thing. Actually, why doesn't the compiler optimize for this scenario. Well, it would have to deal with combinations, not just a simple list of enum values.

  • User profile image
    TommyCarlier

    For mapping stuff like this, I sometimes use a trick I call "switch-as-a-dictionary": a function that just contains a switch-statement that works like dictionary, but a bit faster. I usually make it into an extension method, like this:

    public static Color ToColor(this FlyState state)
    {
      switch (state)
      {
        case FlyState.Charging: return Color.Green;
        case FlyState.Sensitive: return ...;
        case ...;
        default: return Color.White;
      }
    }

    Then you can just call it like this:

    Color c = state.ToColor();

  • User profile image
    exoteric

    TommyCarlier said:

    For mapping stuff like this, I sometimes use a trick I call "switch-as-a-dictionary": a function that just contains a switch-statement that works like dictionary, but a bit faster. I usually make it into an extension method, like this:

    public static Color ToColor(this FlyState state)
    {
      switch (state)
      {
        case FlyState.Charging: return Color.Green;
        case FlyState.Sensitive: return ...;
        case ...;
        default: return Color.White;
      }
    }

    Then you can just call it like this:

    Color c = state.ToColor();

    This is probably the best solution possible today. It will be better (looking) when/if C# gets extension properties.

     

    I wonder a bit about the naming aspect of ToThis and ToThat, in a way it sounds a bit "transformational", vs AsColor() and AsString() where you are asking for new objects of something, where the new version is somehow related (by convention as here, or otherwise). With extension properties it would just be flyState.Color. On the other hand, maybe "To" implies a weaker relationship than "As" (weaker is better; no assumptions).

  • User profile image
    Tokter

    I came across the same problem when I did some XNA stuff. You can supply your dictionary with a comparer object during construction which eliminates the boxing/unboxing. Nick's blog post helped me:

     

    http://nickgravelyn.com/2009/04/net-misconceptions-part-1/

  • User profile image
    TommyCarlier

    exoteric said:
    TommyCarlier said:
    *snip*

    This is probably the best solution possible today. It will be better (looking) when/if C# gets extension properties.

     

    I wonder a bit about the naming aspect of ToThis and ToThat, in a way it sounds a bit "transformational", vs AsColor() and AsString() where you are asking for new objects of something, where the new version is somehow related (by convention as here, or otherwise). With extension properties it would just be flyState.Color. On the other hand, maybe "To" implies a weaker relationship than "As" (weaker is better; no assumptions).

    I usually use “To” for conversion functions, just like ToString, or the methods in the Convert-class (ToInt32, ToDateTime, …).

  • User profile image
    exoteric

    TommyCarlier said:
    exoteric said:
    *snip*

    I usually use “To” for conversion functions, just like ToString, or the methods in the Convert-class (ToInt32, ToDateTime, …).

    As a convention that's good and mandated, I was just wondering if As is better than To, given a pre-convention time; I'm not sure it is though. This is called out in Eiffel and due to Eiffel's conventions, you can call this method without paranthesization because the mehod takes no arguments. Looks pretty neat in code.

     

    http://www.eiffel-nice.org/standards/wip/any/ise50.html

  • User profile image
    Sven Groot

    exoteric said:
    TommyCarlier said:
    *snip*

    As a convention that's good and mandated, I was just wondering if As is better than To, given a pre-convention time; I'm not sure it is though. This is called out in Eiffel and due to Eiffel's conventions, you can call this method without paranthesization because the mehod takes no arguments. Looks pretty neat in code.

     

    http://www.eiffel-nice.org/standards/wip/any/ise50.html

    I would use "As" when the result still somehow contains or refers to the original object, as with List<T>.AsReadOnly; the result there isn't a conversion, it's a wrapper of the original list.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.