multithreading - Más rápido TMultiReadExclusiveWriteSynchronizer?
delphi critical-section (5)
¿Prueba esto? Se puede usar como una variable normal:
type myclass=class
Lock:TOBRWLock;
function ReadIt:Integer;
procedure WriteIt(A:Integer);
end;
function ReadIt:Integer;
begin;
Lock.LockRead;
Result:=GetVal;
Lock.UnLockRead;
end;
Hay mucho espacio para mejorar y puedes construir desde aquí variedades que favorecen la lectura anterior o simplemente actuar de manera diferente según la necesidad.
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;
¿Existe un tipo más rápido de TMultiReadExclusiveWriteSynchronizer
por ahí? ¿FastCode quizás?
Comenzando con Windows Vista, Microsoft agregó un bloqueo Slim Reader / Writer . Funciona mucho mejor que TMultiReadExclusiveWriteSynchronizer
de Delphi. Lamentablemente, solo existe en Windows Vista y posteriormente, algo que pocos clientes realmente tienen todavía.
Es de suponer que los conceptos en uso dentro de un Slim Reader/Writer lock
podrían volver a reproducirse en el código nativo de Delphi, pero ¿alguien lo ha hecho?
Tengo una situación en la que adquirir y liberar bloqueos en un TMultiReadExclusiveWriteSynchronizer
(incluso cuando no hay contención, un único hilo), provoca un 100% de sobrecarga (el tiempo de operación se duplica). Puedo correr sin bloquear, pero luego mi clase ya no es segura para subprocesos.
¿Hay un TMultiReadExclusiveWriteSynchronizer
más TMultiReadExclusiveWriteSynchronizer
?
Nota : si uso TCriticalSection
solo sufriré un 2% de rendimiento (aunque se sabe que las secciones críticas son rápidas cuando la adquisición tiene éxito, es decir, mientras tiene un solo hilo y no hay contienda). La desventaja de una CS es que pierdo la capacidad de " lectores múltiples ".
Las medidas
Al usar TMultiReadExclusiveWriteSynchronizer
una considerable cantidad de tiempo dentro de BeginRead
y EndRead
:
Luego porté el código para usar el propio SlimReaderWriter Lock de Windows (que algunos reescriben, ya que no admite el bloqueo recursivo), y se perfilaron los resutls:
TMultiReadExclusiveWriteSynchronizer
: 10.698 ns por iteración
10,697,772,613 ns para iterar 1,000,000 vecesSRWLock
:SRWLock
ns por iteración
8,801,678,339 ns para iterar 1,000,000 vecesOmni Reader-Writer lock
: 8.941 ns por iteración
8,940,552,487 ns para iterar 1,000,000 veces
Una mejora del 17% cuando se usa SRWLocks (también conocido como el bloqueo giratorio de Omni).
Ahora, no puedo cambiar el código de forma permanente para usar Windows Vista SRWLocks , ya que hay algunas empresas enteras de clientes que todavía están en Windows XP.
Los bloqueos Slim son solo uso cuidadoso de las funciones InterlockedCompareExchange
; pero más cuidadoso de lo que puedo usar con éxito. Estoy tan lejos de solo robar las 140 instrucciones de la máquina involucradas, y hacerlo.
Lectura de bonificación
Al final utilicé una solución de compromiso. El bloqueo Omni
reader-writer utiliza principios "delgados" (manipulación de bits giratorios). Al igual que el de Window, no es compatible con la escalada de bloqueo. Lo probé y parece que no se bloqueó ni se bloqueó.
Al final usé una situación de repliegue. La más genérica de las interfaces genéricas para admitir conceptos de "lectura-escritura" :
IReaderWriterLock = interface
[''{6C4150D0-7B13-446D-9D8E-866B66723320}'']
procedure BeginRead;
procedure EndRead;
procedure BeginWrite;
procedure EndWrite;
end;
Y luego decidimos en tiempo de ejecución qué implementación usar. Si estamos en Windows Vista o en versiones nuevas, use el propio SlimReaderWriter
, de lo contrario SlimReaderWriter
a la versión de 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;
Nota : Cualquier código se libera en el dominio público. No se requiere atribución.
Delphi TMultiReadExclusiveWriteSynchronizer
es muy sofisticado: se puede adquirir recursivamente y puede actualizar desde Read
a Write
.
Esto tiene un costo, que en este caso significa administrar un depósito de estado compartido por hilo. Como la mecánica local de subprocesos de Windows (accesible a través de threadvar
) es demasiado simplista para esto (no es capaz de hacer frente a varias instancias MREWS) se hace de una manera bastante ineficaz - ver las fuentes RTL o JCL - las implementaciones son bastante similares, compartiendo malas el rendimiento y el riesgo de bloqueo de la actualización.
Primero asegúrese de que realmente necesita la funcionalidad MREWS. Supongo que, de acuerdo con el tamaño proporcional de la carga de trabajo de la carga de trabajo, será mucho mejor con TCriticalSection
.
Si realmente lo necesita, vaya a la implementación de Delphi y tenga cuidado con el posible desbloqueo oculto en BeginWrite
: consulte su documentación y el significado del valor de retorno.
Es posible implementar un SRW
similar a Vista utilizando las funciones Interlocked
o el ensamblaje en línea, pero no vale la pena el esfuerzo en la mayoría de los casos.
El JCL tiene un MREWS que es una implementación diferente que podría funcionar para usted. No estoy seguro de qué versión de Windows requiere.
http://wiki.delphi-jedi.org/wiki/JCL_Help:TJclMultiReadExclusiveWrite
http://wiki.delphi-jedi.org/index.php?title=JEDI_Code_Library
TOmniMREW
de OmniThreadLibrary
afirma ser más rápido y ligero:
OTL es una excelente versión de threading lib, BTW.
Código de muestra
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;