更快的TMultiReadExclusiveWriteSynchronizer?

时间:2012-04-30 03:49:50

标签: multithreading delphi critical-section readerwriterlockslim readerwriterlock

那里有{em>更快种TMultiReadExclusiveWriteSynchronizer吗? FastCode也许?

从Windows Vista开始,Microsoft添加了Slim Reader/Writer lock。它performs much better比Delphi的TMultiReadExclusiveWriteSynchronizer。不幸的是,它只存在于Windows Vista及更高版本中,这是很少有客户真正拥有的东西。

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

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

是否有更快的TMultiReadExclusiveWriteSynchronizer

注意:如果我使用TCriticalSection我只会遭受2%的性能下降(虽然当获取成功时,已知关键部分会很快,即,虽然它是单线程的,但没有争用)。 CS的缺点是我失去了“多个读者”的能力。

测量

TMultiReadExclusiveWriteSynchronizerBeginRead内使用EndRead花费了相当多的时间:

enter image description here

然后移植代码以使用Window自己的 SlimReaderWriter Lock (其中一些代码重写,因为它不支持递归锁定),并分析了resutls:

  • 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台机器指令,并完成它。

奖金阅读

5 个答案:

答案 0 :(得分:6)

来自OmniThreadLibrary

TOmniMREW声称更快,更轻量级:

OTL是一个优秀的线程库,BTW。

示例代码

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;

答案 1 :(得分:3)

最后,我使用了妥协解决方案。 Omni读写器锁使用“slim”原则(旋转位操作)。像Window自己一样,它不支持锁升级。我已经测试了它,似乎 lockup 崩溃或死锁。

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

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;
  

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

答案 2 :(得分:2)

Delphi TMultiReadExclusiveWriteSynchronizer非常复杂 - 可以递归获取,您可以从Read更新为Write

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

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

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

可以使用SRW函数或内联汇编实现类似Vista的Interlocked,但在大多数情况下,这是不值得的。

答案 3 :(得分:0)

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

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

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

答案 4 :(得分: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;