Dave New
Dave New

Reputation: 40002

Building a Cache Key quickly

I am using a custom cache implementation in a Web Api 2 application. This cache stores hundreds of thousands of items and can be read from up to 10,000 times in a single API request.

On profiling, I have found that the actual building of each items' cache key is significantly affecting overall performance.

Result from .NET profiling:

Sampling Profiler

Cache key details:

I am building a the items' key by hashing a string. E.g:

MySystem.MyProject.MyNamespace.MyClass.SomeMethod(44,6948)

This gets hashed into something like this, which is then used in the caching framework as the key (this is not longer used - refer to EDIT 3):

1bbbfeae-b143-77f2-8381-5ee11f5b9c0c 

Obviously I need to ensure uniqueness on each key, but I can't seem to find a way to improve the performance here without introducing possible duplication.

The key builder:

public class CacheKeyBuilder
{
    private MethodInterceptionArgs methodArguments;

    public CacheKeyBuilder(MethodInterceptionArgs input)
    {
        methodArguments = input;
    }

    // No longer used - refer to EDIT 3
    public UInt64 GetHashedKey()
    {
        return Hash(GetFriendlyKey());
    }

    public string GetFriendlyKey()
    {
        if (methodArguments.Arguments.OfType<IList>().Any())
        {
            throw new ArgumentOutOfRangeException("Cannot create a keys from IList types");
        }

        var type = methodArguments.Binding.GetType();

        var key = String.Format("{0}.{1}.{2}{3}{4}",
            type.Namespace,
            type.DeclaringType.Name,
            methodArguments.Method.Name,
            type.UnderlyingSystemType.GenericTypeArguments.Select(x => x.Name).ToList().JoinItems("<", ">", ","),
            methodArguments.Arguments.Where(x => x != null).Select(x => x.ToString()).ToList().JoinItems("(", ")", ",")
        );

        return key;
    }

    // No longer used - refer to EDIT 3
    private UInt64 Hash(string key)
    {
        UInt64 hashedValue = 3074457345618258791ul;

        for (int i = 0; i < key.Length; i++)
        {
            hashedValue += key[i];
            hashedValue *= 3074457345618258799ul;
        }

        return hashedValue;
    }
}

Considerations:

Can anyone spot any evident performance improvements that can be made?

EDIT:

Another consideration, based on David and Patryk's comments, is that I cannot hard code the "type" string. The performance improvements need to be backwards compatible. I have to work with reflection.

EDIT 2:

Sorry, the hash methods are meant to return UInt64. Code fixed.

EDIT 3:

Storing the hashed key vs the friendly key has made no difference in performance. Thus, I am moving to the only using GetFriendly(). Thanks usr.

Upvotes: 3

Views: 4106

Answers (1)

David Crowell
David Crowell

Reputation: 3813

It looks like your using PostSharp. Their own example for caching generates the method name as a string at compile time.

It seems you could get the fully-qualified type name at the same time. This would allow the expensive reflection only to occur at compile time.

public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
    _methodName = method.Name;
    _typeName = method.Binding.GetType().Namespace...  ..Name; // etc
}

I would also try the StringBuilder.Append() vs string.Format() and see if there's a peformance difference.

Upvotes: 2

Related Questions