Подтвердить что ты не робот

Ускоренный TMultiReadExclusiveWriteSynchronizer?

Есть ли более быстрый вид TMultiReadExclusiveWriteSynchronizer там? Возможно, FastCode?

Начиная с Windows Vista, Microsoft добавила Slim Reader/Writer lock. Он работает намного лучше, чем Delphi TMultiReadExclusiveWriteSynchronizer. К сожалению, он существует только в Windows Vista и более поздних версиях, чего у немногих клиентов на самом деле еще нет.

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

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

Есть ли более быстрый TMultiReadExclusiveWriteSynchronizer?

Примечание. Если я использую TCriticalSection, я получаю только 2% -ный удар по производительности (хотя критические разделы, как известно, бывают быстрыми, когда приобретает успех, то есть пока он однопоточен и нет конкуренции). Недостатком CS является то, что я теряю возможность "нескольких читателей".

Измерения

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

enter image description here

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

  • TMultiReadExclusiveWriteSynchronizer: 10,698 нс на итерацию
    10 697 772 613 нс для повторения 1 000 000 раз

  • SRWLock: 8802 нс на итерацию
     8,801,678,339 нс для итерации 1 000 000 раз

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

Улучшение на 17% при использовании SRWLocks (также известный как Omni spinning lock).

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

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

Чтение бонусов

4b9b3361

Ответ 1

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

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;

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 5

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

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;