Ускоренный 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 машинных инструкций и сделать это.
Чтение бонусов
Ответы
Ответ 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
или встроенной сборки, но это не стоит усилий в большинстве случаев.
Ответ 4
JCL имеет MREWS, который представляет собой другую реализацию, которая может сработать для вас. Не знаете, какую версию Windows требуется.
http://wiki.delphi-jedi.org/wiki/JCL_Help:TJclMultiReadExclusiveWrite
http://wiki.delphi-jedi.org/index.php?title=JEDI_Code_Library
Ответ 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;