3
\$\begingroup\$

I wanted to measure the performance of Concurrent Dictionary vs Dictionary+Locks in a multithreaded environment. So I created my own SyncDict class of type<int,int[]>. Whenever there is a key match, it adds the int[] array value to itself, it also locks the whole dictionary with ReaderWriterLockSlim while updating the value.

I replicated the whole code through Concurrent Dictionary and I am using AddOrUpdate() method.

Whole console app code can be found here https://dotnetfiddle.net/1kFbGy Just copy paste the code in console app to run. It will not run fiddle

After running both codes with the same inputs I see a considerable amount of difference in running time. For example for one particular run on my machine Concurrent dictionary took 4.5 seconds vs SyncDict took less than 1 second.

I would like to know any thoughts / suggestions explaining the above running time. Is there anything wrong am I doing here?

 class SyncDict<TKey>
    {
        private ReaderWriterLockSlim cacheLock;
        private Dictionary<TKey, int[]> dictionary;
        public SyncDict()
        {
            cacheLock = new ReaderWriterLockSlim();
            dictionary = new Dictionary<TKey, int[]>();
        }

        public Dictionary<TKey, int[]> Dictionary
        {
            get { return dictionary; }
        }

        public int[] Read(TKey key)
        {
            cacheLock.EnterReadLock();
            try
            {
                return dictionary[key];
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
        }

        public void Add(TKey key, int[] value)
        {
            cacheLock.EnterWriteLock();
            try
            {
                dictionary.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

        public AddOrUpdateStatus AddOrUpdate(TKey key, int[] value)
        {
            cacheLock.EnterUpgradeableReadLock();
            try
            {
                int[] result = null;
                if (dictionary.TryGetValue(key, out result))
                {
                    if (result == value)
                        return AddOrUpdateStatus.Unchanged;
                    else
                    {
                        cacheLock.EnterWriteLock();
                        try
                        {
                            Parallel.For(0, value.Length,
                            (i, state) =>
                            {
                                result[i] = result[i] + value[i];
                            });
                        }
                        finally
                        {
                            cacheLock.ExitWriteLock();
                        }
                        return AddOrUpdateStatus.Updated;
                    }
                }
                else
                {
                    Add(key, value);
                    return AddOrUpdateStatus.Added;
                }
            }
            finally
            {
                cacheLock.ExitUpgradeableReadLock();
            }
        }

        public void Delete(TKey key)
        {
            cacheLock.EnterWriteLock();
            try
            {
                dictionary.Remove(key);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

        public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };
    }
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Comparison

  • A comparison of performance of 2 classes only makes sense if both adhere to the same specification. Does your class do what a ConcurrentDictionary does?

Review

  • Why would you allow access to the underlying dictionary? If you must allow it, return a IReadOnlyDictionary.
  • Checking arguments before taking a lock prevents unnecessary locks on bad input.
  • AddOrUpdate does not work; have you checked if (result == value)?
int[] a = new int[]{ 0, 1 }; 
int[] b = new int[]{ 0, 1 }; 
Console.WriteLine(a == b);  // False

Or is this as designed?

  • What is the purpose of this? What if both arrays have different size?
 Parallel.For(0, value.Length,
 (i, state) =>
 {
     result[i] = result[i] + value[i];
 });
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.