문제

나는 컬렉션과 스레딩을 가지고 놀았으며 사람들이 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

일반적인 결과는 다음과 같습니다.

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 (감시 장치).

도움이 되었습니까?

해결책

수백만 개의 스트러크를 인스턴스화하는 가격과 추가 호출의 가격처럼 보입니다.

나는이 샘플에서 readerwriterlockslim이 오용되고 있다고 말하면서,이 경우 자물쇠가 충분하며 readerwriterlockslim과 함께하는 성능 에지는 주니어 개발자에게 이러한 개념을 설명하는 가격에 비해 무시할 수 있습니다.

당신은 얻는다 거대한 독자 작가 스타일 잠금 장치가 읽기 및 쓰기를 수행하는 데 무시할 수없는 시간이 걸릴 때 이점을 얻습니다. 주로 읽기 기반 시스템이있을 때 부스트가 가장 커질 것입니다.

스레드를 삽입 해보십시오. 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();
        }
    }
}

다른 팁

코드는 객체 생성 오버 헤드를 피하기 위해 구조물을 사용하는 것으로 보이지만이 가볍게 유지하기 위해 필요한 다른 단계를 수행하지는 않습니다. 나는 그것이 반환 값을 상자에 상상한다고 믿는다 ReadLock, 그리고 그렇다면 구조물의 전체 이점을 무효화합니다. 이것은 모든 문제를 해결하고 IDisposable 상호 작용.

편집 : 벤치 마크가 요구되었습니다. 이 결과는 정규화됩니다 수동 방법 (호출 Enter/ExitReadLock 그리고 Enter/ExitWriteLock 보호 코드와의 인라인)의 시간 값은 1.00입니다. 원래 방법은 수동 방법이 그렇지 않은 힙에 객체를 할당하기 때문에 느립니다. 이 문제를 해결했으며 릴리스 모드에서는 확장 메소드 호출 오버 헤드조차도 수동 메소드만큼 빠르게 남겨 둡니다.

디버그 빌드 :

Manual:              1.00
Original Extensions: 1.62
My Extensions:       1.24

릴리스 빌드 :

Manual:              1.00
Original Extensions: 1.51
My Extensions:       1.00

내 코드 :

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();
        }
    }
}

내 추측 (확인하기 위해 프로필이 필요합니다)은 성능 낙하가 일회용 인스턴스를 만드는 데 비롯된 것이 아니라는 것입니다 (그들은 상당히 저렴해야합니다. 대신 나는 그것이 행동 대의원을 만드는 것입니다. 일회용 구조의 구현을 변경하여 액션 대의원을 만드는 대신 ReaderWriterlockSlim의 인스턴스를 저장할 수 있습니다.

편집하다: @280z28의 게시물은 둔화를 일으키는 행동 대의원의 힙 할당임을 확인합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top