22

有没有更快的那种TMultiReadExclusiveWriteSynchronizer?也许是 FastCode?

从 Windows Vista 开始,Microsoft 添加了Slim Reader/Writer lock。它的性能比 Delphi 的TMultiReadExclusiveWriteSynchronizer. 不幸的是,它只存在于 Windows Vista 及更高版本中,实际上很少有客户拥有。

大概 a 中使用的概念Slim Reader/Writer lock可以在本机 Delphi 代码中重做 - 但有人做过吗?

我有一种情况,在 a 上获取和释放锁TMultiReadExclusiveWriteSynchronizer(即使没有争用 - 单个线程)会导致 100% 的开销(操作时间加倍)。我可以在没有锁定的情况下运行,但是我的类不再是线程安全的。

有更快的TMultiReadExclusiveWriteSynchronizer吗?

注意:如果我使用 aTCriticalSection我只会遭受 2% 的性能损失(尽管已知关键部分在获取成功时会很快,即当它是单线程并且没有争用时)。CS 的缺点是我失去了“多个阅读器”的能力。

测量结果

使用TMultiReadExclusiveWriteSynchronizer相当多的时间花在里面BeginReadand EndRead

在此处输入图像描述

然后,我将代码移植到使用 Window 自己的SlimReaderWriter 锁(一些代码重写,因为它不支持递归锁获取),并分析了结果:

  • TMultiReadExclusiveWriteSynchronizer: 10,698 ns 每次迭代
    10,697,772,613 ns 迭代 1,000,000 次

  • SRWLock: 8,802 ns 每次迭代
    8,801,678,339 ns 迭代 1,000,000 次

  • Omni Reader-Writer lock: 8,941 ns 每次迭代
    8,940,552,487 ns 迭代 1,000,000 次

使用 SRWLocks(又名 Omni 的旋转锁)时提高了 17%。

现在,我无法永久切换代码以使用Windows Vista SRWLocks,因为有一些完整的客户企业仍在使用 Windows XP。

Slim 锁只是对InterlockedCompareExchange功能的谨慎使用;但比我能成功使用的要小心。我离窃取所涉及的 140 条机器指令还差得很远,并且已经完成了

奖金阅读

4

5 回答 5

5

TOmniMREWOmniThreadLibrary声称更快,更轻量级:

OTL 是一个优秀的线程库,顺便说一句。

示例代码

TOmniReaderWriterLock = class(TInterfacedObject, IReaderWriterLock)
private
   omrewReference: Integer;
public
   { IReaderWriterLock }
   procedure BeginRead;
   procedure EndRead;
   procedure BeginWrite;
   procedure EndWrite;
end;

{ TOmniReaderWriterLock }

procedure TOmniReaderWriterLock.BeginRead;
var
  currentReference: Integer;
begin
    //Wait on writer to reset write flag so Reference.Bit0 must be 0 than increase Reference
    repeat
        currentReference := Integer(omrewReference) and not 1;
    until currentReference = Integer(InterlockedCompareExchange(Pointer(omrewReference), Pointer(Integer(currentReference) + 2), Pointer(currentReference)));
end;

procedure TOmniReaderWriterLock.EndRead;
begin
    //Decrease omrewReference
    InterlockedExchangeAdd(@omrewReference, -2);
end;

procedure TOmniReaderWriterLock.BeginWrite;
var
    currentReference: integer;
begin
    //Wait on writer to reset write flag so omrewReference.Bit0 must be 0 then set omrewReference.Bit0
    repeat
        currentReference := omrewReference and (not 1);
    until currentReference = Integer(InterlockedCompareExchange(Pointer(omrewReference), Pointer(currentReference+1), Pointer(currentReference)));

    //Now wait on all readers
    repeat
    until omrewReference = 1;
end;

procedure TOmniReaderWriterLock.EndWrite;
begin
    omrewReference := 0;
end;
于 2013-09-13T06:21:13.227 回答
3

最后我使用了一个折衷的解决方案。Omni读写锁使用“ slim”原则(旋转位操作)。像 Window 自己的一样,它不支持锁升级。我已经测试过了,它似乎没有锁定崩溃死锁。

最后,我使用了后备情况。支持“读写”概念的最通用的通用接口:

IReaderWriterLock = interface
   ['{6C4150D0-7B13-446D-9D8E-866B66723320}']
   procedure BeginRead;
   procedure EndRead;
   procedure BeginWrite;
   procedure EndWrite;
end;

然后我们在运行时决定使用哪个实现。如果我们使用的是 Windows Vista 或新版本,请使用 Window 自己的SlimReaderWriter,否则回退到Omni版本:

TReaderWriterLock = class(TObject)
public
   class function Create: IReaderWriterLock;
end;

class function TReaderWriterLock.Create: IReaderWriterLock;
begin
   if Win32MajorVersion >= 6 then //SRWLocks were introduced with Vista/Server 2008 (Windows version 6.0)
   begin
      //Use the Windows built-in Slim ReaderWriter lock
      Result := TSlimReaderWriterLock.Create;
   end
   else
   begin
      //XP and earlier fallback to Omni equivalent
      Result := TOmniReaderWriterLock.Create;
   end;
end;

注意:任何代码都会发布到公共领域。无需归属。

于 2013-10-28T19:03:40.840 回答
2

DelphiTMultiReadExclusiveWriteSynchronizer非常复杂 - 它可以递归获取,并且您可以从 更新ReadWrite

这带来了成本,在这种情况下,这意味着每个线程管理一个共享状态桶。由于 Windows 线程本地机制(可通过 访问threadvar)对此过于简单(无法处理多个 MREWS 实例),它以一种相当低效的方式完成 - 请参阅 RTL 或 JCL 源 - 实现非常相似,共享较差的性能和更新死锁风险。

首先确保您确实需要 MREWS 功能——我假设,根据锁定开销与工作负载的比例大小,使用TCriticalSection.

如果您真的需要它,请使用 Delphi 实现并注意可能隐藏的解锁BeginWrite- 请参阅它的文档和返回值含义。

SRW可以使用函数或内联程序集来实现类似于 Vista的Interlocked功能,但在大多数情况下不值得付出努力。

于 2012-10-04T20:41:18.273 回答
0

JCL 有一个 MREWS,它是一种不同的实现,可能对您有用。不确定它需要什么版本的 Windows。

http://wiki.delphi-jedi.org/wiki/JCL_Help:TJclMultiReadExclusiveWrite

http://wiki.delphi-jedi.org/index.php?title=JEDI_Code_Library

于 2012-06-26T15:59:53.597 回答
0

试试这个?它可以用作普通变量:

type myclass=class
              Lock:TOBRWLock;
              function ReadIt:Integer;
              procedure WriteIt(A:Integer);
             end;  
function ReadIt:Integer;
begin;
 Lock.LockRead;
 Result:=GetVal;
 Lock.UnLockRead;
end;

有很大的改进空间,您可以从这里构建有利于读写的品种,或者根据需要采取不同的行动。

const ldFree    = 0;
      ldReading = 1;
      ldWriting = 2;
type TOBRWLock          = record
                 [Volatile]WritersWaiting,
                           ReadersWaiting,
                           ReadersReading,
                           Disposition    : Integer;
                           procedure LockRead;
                           procedure LockWrite;
                           procedure UnlockRead;
                           procedure UnlockWrite;
                           procedure UnReadWrite;
                           procedure UnWriteRead;
                          end;

procedure TOBRWLock.LockRead;
var SpinCnt : NativeUInt;
    I       : Integer;
begin
 SpinCnt:=0;
 TInterlocked.Increment(ReadersWaiting);
 repeat
  if (Disposition=ldReading)
     then begin
           I:=TInterlocked.Increment(ReadersReading);
           if (Disposition<>ldReading) or (I=1)(*Only 1 read reference or Disposition changed, suspicious, rather retry*)
              then begin
                    TInterlocked.Decrement(ReadersReading);
                    continue;
                   end
              else begin(*Success*)
                    TInterlocked.Decrement(ReadersWaiting);
                    break;
                   end;
          end;
  if (WritersWaiting<>0)or(Disposition<>ldFree)
     then begin
           SpinBackoff(SpinCnt);
           continue;
          end;
  if TInterlocked.CompareExchange(Disposition,ldReading,ldFree)=ldFree
     then begin
           TInterlocked.Increment(ReadersReading);
           TInterlocked.Decrement(ReadersWaiting);
           break;
          end;
  SpinBackoff(SpinCnt);
 until False;
end;

procedure TOBRWLock.LockWrite;
var SpinCnt : NativeUInt;
begin
 SpinCnt:=0;
 TInterlocked.Increment(WritersWaiting);
 repeat
  if (Disposition<>ldFree)
     then begin
           SpinBackoff(SpinCnt);
           continue;
          end;
  if TInterlocked.CompareExchange(Disposition,ldWriting,ldFree)=ldFree
     then begin
           TInterlocked.Decrement(WritersWaiting);
           break;
          end
     else SpinBackoff(SpinCnt);
 until False;
end;

procedure TOBRWLock.UnlockRead;
begin
 {$IFDEF DEBUG}
 if Disposition<>ldReading
    then raise Exception.Create('UnlockRead a lock that is not Reading');
 {$ENDIF}
 TInterlocked.Decrement(ReadersReading);
 if ReadersReading=0
    then begin;
          if TInterlocked.CompareExchange(Disposition,ldFree,ldReading)<>ldReading
             then raise Exception.Create('Impossible 310');
         end;
end;

procedure TOBRWLock.UnlockWrite;
begin
 {$IFDEF DEBUG}
 if Disposition<>ldWriting
    then raise Exception.Create('UnlockWrite a lock that is not Writing');
 {$ENDIF}
 if TInterlocked.CompareExchange(Disposition,ldFree,ldWriting)<>ldWriting
    then raise Exception.Create('Impossible 321');
end;

procedure TOBRWLock.UnReadWrite;
var SpinCnt : NativeUInt;
begin
 {$IFDEF DEBUG}
 if Disposition<>ldReading
    then raise Exception.Create('UnReadWrite a lock that is not Reading');
 {$ENDIF}
 TInterlocked.Increment(WritersWaiting);
 SpinCnt:=0;
 repeat
  if ReadersReading=1(*Only me reading*)
     then begin;
           if TInterlocked.CompareExchange(Disposition,ldWriting,ldReading)<>ldReading(*Must always succeed*)
              then raise Exception.Create('Impossible 337');
           TInterlocked.Decrement(ReadersReading);
           TInterlocked.Decrement(WritersWaiting);
           break;
          end;
  SpinBackoff(SpinCnt);
 until False;
end;
于 2014-01-20T11:28:48.547 回答