Fast case-insensitive hash-code for specific codes I'm trying to fix another bottleneck identified with a profiler (dotTrace). This time it's a case insensitive hash-code. 

Currently I'm using the StringComparer.OrdinalIgnoreCase as a comparer for a dictionary and a custom  implementaion of an IComparable&lt;string&gt;. With 40 millions calls it costs me ~14 seconds. This is too much as there will be more calls in future. I'd like to replace it with an int to improve the lookup time.

The values I'm working with are in the format and between 1A and 99Z. There will never be other codes.

I've build a proof-of-concept for encoding and decoding these codes that works like that: 

I shift each char to the left by 8 bits from left to right: for example 5A and 20F. 

               5        A
       2       0        F
-------- ------- --------


The decoder unshifts them and rebuilds the string (mainly for debugging purposes as I actually won't be doing this much). Additionaly if a letter exceeds the A-Z alphabet its case is fixed to upper-case.

static class CaseInsensitiveCoordinate
{
    private const int AlphabetLength = ('Z' - 'A');
    private const int AlphabetsDistance = ('a' - 'Z');
    private const int CaseFix = AlphabetLength + AlphabetsDistance;

    public static int Encode(this string value)
    {
        var result = 0;

        // Assume the code has only two chars.
        result += (int)value[0] &lt;&lt; 8;

        // Oh, there are tree, then shift what we have and add the next one.
        if (value.Length == 3)
        {
            result &lt;&lt;= 8;
            result += (int)value[1] &lt;&lt; 8;
        }

        // Finally add the letter and fix the casing.
        var letter = (int)value[value.Length - 1];
        if (letter &gt; 'Z')
        {
            letter -= CaseFix;
        }

        return result += letter;
    }

    public static string Decode(this int value)
    {
        // Unshift all values and rebuild the string.
        return
            (value &gt; "9Z".Encode())
                ? new string(new[]
                {
                    (char)((value &amp; 0xFF0000) &gt;&gt; 16),
                    (char)((value &amp; 0xFF00) &gt;&gt; 8), 
                    (char)(value &amp; 0xFF) 
                })
                : new string(new[] 
                { 
                    (char)((value &amp; 0xFF00) &gt;&gt; 8), 
                    (char)(value &amp; 0xFF) 
                });
    }
}


Simple Stopwatch measurements in LINQPad (compiled as release) result in a difference of about ~1.5 seconds for 40 million loops between the string-comparer and my hash-code.

void Main()
{

    var dic = new Dictionary&lt;string, object&gt;(StringComparer.OrdinalIgnoreCase) { ["5A"] = new object() };
    var dic2 = new Dictionary&lt;int, object&gt; { [13633] = new object() };

    var count = 40_000_000;

    var sw = Stopwatch.StartNew();  
    for (int i = 0; i &lt; count; i++)
    {
        var result = dic["5A"];
    }   
    sw.Elapsed.Dump();

    sw = Stopwatch.StartNew();  
    for (int i = 0; i &lt; count; i++)
    {
        var result = dic2["5A".Encode()];
    }   
    sw.Elapsed.Dump();
}


I call them hash-codes because I'm going to use them as such but at the same time they are a numeric representation of the string-code and they also need to remain sortable (they are use in a SortedHashSet).
There isn't much left to optimize there, just few cycles, but with 40 millions calls the difference might be at least measurable.

When value.length == 3 you perform &lt;&lt; 8 twice and csc or JIT compiler have no chance to optimize this, given that length can be only 2 or 3 then you can make it explicit (I think you will also gain in clarity but that's just a point of view):

public static int Encode(this string value)
{
    Debug.Assert(value != null);
    Debug.Assert(value.Length == 2 || value.Length == 3);

    if (value.Length == 2)
    {
        return ((int)value[0] &lt;&lt; 8)
            | EncodeLetter(value[1]);
    }
    else
    {
        return ((int)value[0] &lt;&lt; 16)
            | ((int)value[1] &lt;&lt; 8)
            | EncodeLetter(value[2]);
    }

    int EncodeLetter(char c)
        =&gt; c &gt; 'Z' ? (return c - CaseFix) : (c);
}


In this way you should save one shift &lt;&lt; (which isn't super-fast in all .NET environments). Also direct access to string items without value.Length - 1 should be slightly faster. Bitwise OR | may be faster than sum + but it depends on an huge amount of details (specific CPU, in primis, and how uop will be fused with surrounding code)...not something we can determine without more details. Here I like it only because of clarity.

It would be nice if we may get the JIT compiler to drop bounds checking here, is unsafe code allowed here?

I added few Debug.Assert(), not a penalty price you want to pay in released code but probably useful when running tests on your own development machine.

About Decode(): "9Z".Encode() might be saved in a static readonly field (in my case it seems it's not optimized away by compiler/JIT).