我一直在玩集合和线程,并且遇到了人们创建的漂亮的扩展方法,通过允许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
正如您所看到的,使用扩展实际上会使性能 WORSE 而不仅仅是使用lock
(监视器)。
答案 0 :(得分: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();
}
}
}
答案 1 :(得分:8)
代码似乎使用结构来避免对象创建开销,但是没有采取其他必要步骤来保持这种轻量级。我相信它会从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();
}
}
}
答案 2 :(得分:2)
我的猜测(您需要进行配置验证)是性能下降不是来自创建Disposable实例(它们应该相当便宜,是结构化)。相反,我希望它来自创建Action代理。您可以尝试更改Disposable结构的实现来存储ReaderWriterLockSlim的实例,而不是创建Action委托。
编辑: @ 280Z28的帖子确认这是导致速度减慢的Action代理的堆分配。