Быстрее TMultiReadExclusiveWriteSynchronizer?

есть быстрее вид TMultiReadExclusiveWriteSynchronizer там? FastCode возможно?

начиная с Windows Vista, Microsoft добавила тонкий замок читателя / писателя. Это выполняет гораздо лучше чем Делфи TMultiReadExclusiveWriteSynchronizer. К сожалению, он существует только в Windows Vista и более поздних версиях, что на самом деле есть у немногих клиентов.

предположительно понятия, используемые внутри Slim Reader/Writer lock может быть переделан в собственном коде Delphi - но есть кто-нибудь сделал?

у меня есть ситуация, когда приобретение и освобождение замков на TMultiReadExclusiveWriteSynchronizer (даже если нет разногласий-один поток), вызывает 100% накладные расходы (время работы удваивается). я могу работать без блокировки, но тогда мой класс больше не является потокобезопасным.

есть быстрее TMultiReadExclusiveWriteSynchronizer?

Примечание: если я использую TCriticalSection Я только страдаю от 2% производительности (хотя критические разделы, как известно, быстро, когда приобрести преуспевает, т. е. пока он однопоточный и нет разногласий). Минусом КС является то, что я теряю "несколько читателей" возможности.

Измерения

используя TMultiReadExclusiveWriteSynchronizer значительное количество времени тратится внутри BeginRead и EndRead:

enter image description here

затем я портировал код, чтобы использовать собственный SlimReaderWriter Замок (который некоторый код переписывает, так как он не поддерживает рекурсивная блокировка), и профилированные resutls:

  • TMultiReadExclusiveWriteSynchronizer: 10,698 НС на итерацию
    10,697,772,613 НС перебрать 1000000 раз

  • SRWLock: 8,802 НС на итерацию
    8,801,678,339 НС перебрать 1000000 раз

  • Omni Reader-Writer lock: 8,941 НС на итерацию
    8,940,552,487 НС для итерации 1,000,000 раз

улучшение 17% при использовании SRWLocks (он же omni's spinning lock).

теперь я не могу постоянно переключать код на использование Windows Vista SRWLocks, так как есть некоторые целые предприятия клиентов, которые все еще находятся на Windows XP.

тонкие замки просто тщательное использование InterlockedCompareExchange функции, но более осторожны, чем я могу успешно использовать. Я этой далеко от просто кражи 140 задействованы машинные инструкции, и дело сделано.

Бонус Чтение

5 ответов


TOmniMREW С OmniThreadLibrary утверждает, что быстрее и легче:

OTL-отличный потоковый lib, кстати.

Пример Кода

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;

в конце концов я использовал компромиссное решение. The Omni блокировка чтения-записи использует "тонкий" принципы (вращающийся бит-манипуляции). Как и собственное окно, оно не поддерживает эскалацию блокировки. Я протестировал его, и он не кажется to блокировка авария или затор.

в конце концов я использовал резервный ситуации. Самый общий из общих интерфейсов для поддержки "чтение-запись" концепции:

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

и тогда мы решите во время выполнения, какую реализацию использовать. Если мы находимся в Windows Vista или new, используйте собственный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;

Примечание: любой код выпущен в общественное достояние. Атрибуция не требуется.


Дельфи TMultiReadExclusiveWriteSynchronizer очень сложный - его можно получить рекурсивно, и вы можете обновить из Read to Write.

это связано с затратами, что в данном случае означает управление ведром общего состояния в потоке. В качестве потока Windows-локальная механика (доступна через threadvar) слишком упрощен для этого (не в состоянии справиться с несколькими экземплярами MREWS) это делается довольно неэффективным способом-см. источники RTL или JCL – реализации довольно похожи, совместное использование плохая производительность и обновление-риск взаимоблокировки.

сначала убедитесь, что вам действительно нужна функциональность MREWS-я предполагаю, что в соответствии с пропорциональным размером блокировки накладных расходов на рабочую нагрузку, вам будет намного лучше с TCriticalSection.

Если вам это действительно нужно, перейдите к реализации Delphi и следите за возможной скрытой разблокировкой в BeginWrite - см. это документация и значение возвращаемого значения.

возможно реализовать Vista-like SRW с помощью Interlocked функции или встроенная сборка, но в большинстве случаев это не стоит усилий.


JCL имеет MREWS, который является другой реализацией, которая может работать для вас. Не уверен, какая версия windows требуется.

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

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


попробовать? Его можно использовать как обычную переменную:

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;