ReaderWriterLockSlim扩展方法性能

Sea*_*ton 8 c# performance extension-methods multithreading locking

我一直在玩集合和线程,并且遇到了人们创建的漂亮的扩展方法,通过允许IDisposable模式来简化ReaderWriterLockSlim的使用.

但是,我相信我已经意识到实施中的某些东西是性能杀手.我意识到扩展方法不应该真正影响性能,所以我假设实现中的某些东西是原因......创建/收集了一次性结构的数量?

这是一些测试代码:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;

namespace LockPlay {

    static class RWLSExtension {
        struct Disposable : IDisposable {
            readonly Action _action;
            public Disposable(Action action) {
                _action = action;
            }
            public void Dispose() {
                _action();
            }
        } // end struct
        public static IDisposable ReadLock(this ReaderWriterLockSlim rwls) {
            rwls.EnterReadLock();
            return new Disposable(rwls.ExitReadLock);
        }
        public static IDisposable UpgradableReadLock(this ReaderWriterLockSlim rwls) {
            rwls.EnterUpgradeableReadLock();
            return new Disposable(rwls.ExitUpgradeableReadLock);
        }
        public static IDisposable WriteLock(this ReaderWriterLockSlim rwls) {
            rwls.EnterWriteLock();
            return new Disposable(rwls.ExitWriteLock);
        }
    } // end class

    class Program {

        class MonitorList<T> : List<T>, IList<T> {
            object _syncLock = new object();
            public MonitorList(IEnumerable<T> collection) : base(collection) { }
            T IList<T>.this[int index] {
                get {
                    lock(_syncLock)
                        return base[index];
                }
                set {
                    lock(_syncLock)
                        base[index] = value;
                }
            }
        } // end class

        class RWLSList<T> : List<T>, IList<T> {
            ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
            public RWLSList(IEnumerable<T> collection) : base(collection) { }
            T IList<T>.this[int index] {
                get {
                    try {
                        _rwls.EnterReadLock();
                        return base[index];
                    } finally {
                        _rwls.ExitReadLock();
                    }
                }
                set {
                    try {
                        _rwls.EnterWriteLock();
                        base[index] = value;
                    } finally {
                        _rwls.ExitWriteLock();
                    }
                }
            }
        } // end class

        class RWLSExtList<T> : List<T>, IList<T> {
            ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
            public RWLSExtList(IEnumerable<T> collection) : base(collection) { }
            T IList<T>.this[int index] {
                get {
                    using(_rwls.ReadLock())
                        return base[index];
                }
                set {
                    using(_rwls.WriteLock())
                        base[index] = value;
                }
            }
        } // end class

        static void Main(string[] args) {
            const int ITERATIONS = 100;
            const int WORK = 10000;
            const int WRITE_THREADS = 4;
            const int READ_THREADS = WRITE_THREADS * 3;

            // create data - first List is for comparison only... not thread safe
            int[] copy = new int[WORK];
            IList<int>[] l = { new List<int>(copy), new MonitorList<int>(copy), new RWLSList<int>(copy), new RWLSExtList<int>(copy) };

            // test each list
            Thread[] writeThreads = new Thread[WRITE_THREADS];
            Thread[] readThreads = new Thread[READ_THREADS];
            foreach(var list in l) {
                Stopwatch sw = Stopwatch.StartNew();
                for(int k=0; k < ITERATIONS; k++) {
                    for(int i = 0; i < writeThreads.Length; i++) {
                        writeThreads[i] = new Thread(p => {
                            IList<int> il = p as IList<int>;
                            int c = il.Count;
                            for(int j = 0; j < c; j++) {
                                il[j] = j;
                            }
                        });
                        writeThreads[i].Start(list);
                    }
                    for(int i = 0; i < readThreads.Length; i++) {
                        readThreads[i] = new Thread(p => {
                            IList<int> il = p as IList<int>;
                            int c = il.Count;
                            for(int j = 0; j < c; j++) {
                                int temp = il[j];
                            }
                        });
                        readThreads[i].Start(list);
                    }
                    for(int i = 0; i < readThreads.Length; i++)
                        readThreads[i].Join();
                    for(int i = 0; i < writeThreads.Length; i++)
                        writeThreads[i].Join();
                };
                sw.Stop();
                Console.WriteLine("time: {0} class: {1}", sw.Elapsed, list.GetType());
            }
            Console.WriteLine("DONE");
            Console.ReadLine();
        }
    } // end class
} // end namespace
Run Code Online (Sandbox Code Playgroud)

这是典型的结果:

time: 00:00:03.0965242 class: System.Collections.Generic.List`1[System.Int32]
time: 00:00:11.9194573 class: LockPlay.Program+MonitorList`1[System.Int32]
time: 00:00:08.9510258 class: LockPlay.Program+RWLSList`1[System.Int32]
time: 00:00:16.9888435 class: LockPlay.Program+RWLSExtList`1[System.Int32]
DONE

正如您所看到的,使用扩展实际上使得性能比仅使用(监视器)更糟糕lock.

Sam*_*ron 9

看起来它是实例化数百万个结构和额外调用的代价.

我甚至可以说ReaderWriterLockSlim在这个示例中被误用了,在这种情况下锁定就足够了,而且与向初级开发人员解释这些概念的价格相比,使用ReaderWriterLockSlim获得的性能优势可以忽略不计.

当读取和写入需要不可忽略的时间时,您可以获得读写器样式锁的巨大优势.当您拥有一个主要基于读取的系统时,提升将是最大的.

在获取锁定时尝试插入Thread.Sleep(1)以查看它产生的巨大差异.

看到这个基准:

Time for Test.SynchronizedList`1[System.Int32] Time Elapsed 12310 ms
Time for Test.ReaderWriterLockedList`1[System.Int32] Time Elapsed 547 ms
Time for Test.ManualReaderWriterLockedList`1[System.Int32] Time Elapsed 566 ms

在我的基准测试中,我并没有真正注意到两种款式之间的差异,我觉得使用它会很舒服,前提是它有一些终结器保护,以防人们忘记处理....

using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System;
using System.Linq;

namespace Test {

    static class RWLSExtension {
        struct Disposable : IDisposable {
            readonly Action _action;
            public Disposable(Action action) {
                _action = action;
            }
            public void Dispose() {
                _action();
            }
        }

        public static IDisposable ReadLock(this ReaderWriterLockSlim rwls) {
            rwls.EnterReadLock();
            return new Disposable(rwls.ExitReadLock);
        }
        public static IDisposable UpgradableReadLock(this ReaderWriterLockSlim rwls) {
            rwls.EnterUpgradeableReadLock();
            return new Disposable(rwls.ExitUpgradeableReadLock);
        }
        public static IDisposable WriteLock(this ReaderWriterLockSlim rwls) {
            rwls.EnterWriteLock();
            return new Disposable(rwls.ExitWriteLock);
        }
    }

    class SlowList<T> {

        List<T> baseList = new List<T>();

        public void AddRange(IEnumerable<T> items) {
            baseList.AddRange(items);
        }

        public virtual T this[int index] {
            get {
                Thread.Sleep(1);
                return baseList[index];
            }
            set {
                baseList[index] = value;
                Thread.Sleep(1);
            }
        }
    }

    class SynchronizedList<T> : SlowList<T> {

        object sync = new object();

        public override T this[int index] {
            get {
                lock (sync) {
                    return base[index];
                }

            }
            set {
                lock (sync) {
                    base[index] = value;
                }
            }
        }
    }


    class ManualReaderWriterLockedList<T> : SlowList<T> {

        ReaderWriterLockSlim slimLock = new ReaderWriterLockSlim();

        public override T this[int index] {
            get {
                T item;
                try {
                    slimLock.EnterReadLock();
                    item = base[index];
                } finally {
                    slimLock.ExitReadLock();
                }
                return item;

            }
            set {
                try {
                    slimLock.EnterWriteLock();
                    base[index] = value;
                } finally {
                    slimLock.ExitWriteLock();
                }
            }
        }
    }

    class ReaderWriterLockedList<T> : SlowList<T> {

        ReaderWriterLockSlim slimLock = new ReaderWriterLockSlim();

        public override T this[int index] {
            get {
                using (slimLock.ReadLock()) {
                    return base[index];
                }
            }
            set {
                using (slimLock.WriteLock()) {
                    base[index] = value;
                }
            }
        }
    }


    class Program {


        private static void Repeat(int times, int asyncThreads, Action action) {
            if (asyncThreads > 0) {

                var threads = new List<Thread>();

                for (int i = 0; i < asyncThreads; i++) {

                    int iterations = times / asyncThreads;
                    if (i == 0) {
                        iterations += times % asyncThreads;
                    }

                    Thread thread = new Thread(new ThreadStart(() => Repeat(iterations, 0, action)));
                    thread.Start();
                    threads.Add(thread);
                }

                foreach (var thread in threads) {
                    thread.Join();
                }

            } else {
                for (int i = 0; i < times; i++) {
                    action();
                }
            }
        }

        static void TimeAction(string description, Action func) {
            var watch = new Stopwatch();
            watch.Start();
            func();
            watch.Stop();
            Console.Write(description);
            Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
        }

        static void Main(string[] args) {

            int threadCount = 40;
            int iterations = 200;
            int readToWriteRatio = 60;

            var baseList = Enumerable.Range(0, 10000).ToList();

            List<SlowList<int>> lists = new List<SlowList<int>>() {
                new SynchronizedList<int>() ,
                new ReaderWriterLockedList<int>(),
                new ManualReaderWriterLockedList<int>()
            };

            foreach (var list in lists) {
                list.AddRange(baseList);
            }


            foreach (var list in lists) {
                TimeAction("Time for " + list.GetType().ToString(), () =>
                {
                    Repeat(iterations, threadCount, () =>
                    {
                        list[100] = 99;
                        for (int i = 0; i < readToWriteRatio; i++) {
                            int ignore = list[i];
                        }
                    });
                });
            }



            Console.WriteLine("DONE");
            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Sam*_*ell 8

代码似乎使用结构来避免对象创建开销,但是没有采取其他必要的步骤来保持这种轻量级.我相信它会从中获取返回值ReadLock,如果是这样,则会否定结构的整体优势.这应该解决所有问题并执行与不通过IDisposable接口一样好.

编辑:基准要求.这些结果被标准化,因此手动方法(使用受保护代码调用Enter/ ExitReadLockEnter/ ExitWriteLock内联)的时间值为1.00.原始方法很慢,因为它在堆上分配手动方法没有的对象.我解决了这个问题,并且在发布模式下,即使扩展方法调用开销也会消失,与手动方法一样快.

调试版本:

Manual:              1.00
Original Extensions: 1.62
My Extensions:       1.24
Run Code Online (Sandbox Code Playgroud)

发布版本:

Manual:              1.00
Original Extensions: 1.51
My Extensions:       1.00
Run Code Online (Sandbox Code Playgroud)

我的代码:

internal static class RWLSExtension
{
    public static ReadLockHelper ReadLock(this ReaderWriterLockSlim readerWriterLock)
    {
        return new ReadLockHelper(readerWriterLock);
    }

    public static UpgradeableReadLockHelper UpgradableReadLock(this ReaderWriterLockSlim readerWriterLock)
    {
        return new UpgradeableReadLockHelper(readerWriterLock);
    }

    public static WriteLockHelper WriteLock(this ReaderWriterLockSlim readerWriterLock)
    {
        return new WriteLockHelper(readerWriterLock);
    }

    public struct ReadLockHelper : IDisposable
    {
        private readonly ReaderWriterLockSlim readerWriterLock;

        public ReadLockHelper(ReaderWriterLockSlim readerWriterLock)
        {
            readerWriterLock.EnterReadLock();
            this.readerWriterLock = readerWriterLock;
        }

        public void Dispose()
        {
            this.readerWriterLock.ExitReadLock();
        }
    }

    public struct UpgradeableReadLockHelper : IDisposable
    {
        private readonly ReaderWriterLockSlim readerWriterLock;

        public UpgradeableReadLockHelper(ReaderWriterLockSlim readerWriterLock)
        {
            readerWriterLock.EnterUpgradeableReadLock();
            this.readerWriterLock = readerWriterLock;
        }

        public void Dispose()
        {
            this.readerWriterLock.ExitUpgradeableReadLock();
        }
    }

    public struct WriteLockHelper : IDisposable
    {
        private readonly ReaderWriterLockSlim readerWriterLock;

        public WriteLockHelper(ReaderWriterLockSlim readerWriterLock)
        {
            readerWriterLock.EnterWriteLock();
            this.readerWriterLock = readerWriterLock;
        }

        public void Dispose()
        {
            this.readerWriterLock.ExitWriteLock();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)