Plus Rapide TMultiReadExclusiveWriteSynchronizer?
y a - t-il une plus rapide sorte de TMultiReadExclusiveWriteSynchronizer
là-bas? FastCode peut-être?
à partir de Windows Vista, Microsoft a ajouté un Slim Reader/Writer lock . effectue beaucoup mieux que Delphi TMultiReadExclusiveWriteSynchronizer
. Malheureusement, il n'existe que dans Windows Vista et plus tard, quelque chose que peu de clients ont réellement encore.
probablement les concepts utilisés à l'intérieur d'un Slim Reader/Writer lock
pourrait être refait en code Delphi natif - mais quelqu'un l'a-t-il fait?
j'ai une situation où l'acquisition et la libération des serrures sur un TMultiReadExclusiveWriteSynchronizer
(même s'il n'y a pas de contestation - un seul fil), provoque 100% de dépassement (le temps de fonctionnement Double). je peux courir sans fermeture, mais ma classe n'est plus sûre.
y a-t-il un TMultiReadExclusiveWriteSynchronizer
plus rapide ?
Note : si j'utilise un TCriticalSection
je souffre seulement une performance de 2% hit (bien que les sections critiques sont connues pour être rapide quand le acquérir succède, c.-à-d. alors qu'il est simple fileté et il n'y a pas de contestation). L'inconvénient D'un CS est que je perds la " lecteurs multiples " capacité.
Les Mesures
par TMultiReadExclusiveWriteSynchronizer
un temps considérable est passé à l'intérieur de BeginRead
et EndRead
:
j'ai ensuite porté le code pour utiliser le propre Slimreaderwriter Lock (que certains réécrivent le code, car il ne supporte pas la prise de verrouillage récursive), et profilé les résultats:
-
TMultiReadExclusiveWriteSynchronizer
: 10,698 ns par itération
10 697 772 613 ns pour itérer 1 000 000 fois -
SRWLock
: 8,802 ns par itération
8 801 678 339 ns pour itérer 1 000 000 fois -
Omni Reader-Writer lock
: 8,941 ns par itération
8 940 552 487 ns pour itérer 1 000 000 fois
amélioration de 17% lors de L'utilisation de SRWLocks (alias omni's spinning lock).
maintenant, je ne peux pas changer le code de façon permanente à utiliser Windows Vista SRWLocks , car il ya des entreprises entières de clients qui sont encore sur Windows XP.
les serrures minces sont juste utilisation prudente de InterlockedCompareExchange
fonctions, mais plus prudent que je ne peux utiliser avec succès. Je suis ce loin de voler les 140 instructions de la machine impliquées, et que ce soit fait.
Bonus La lecture de
5 réponses
TOmniMREW
de OmniThreadLibrary
prétend être plus rapide et plus léger:
OTL est un excellent filetage lib, BTW.
Code Échantillon
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;
À la fin, j'ai utilisé une solution de compromis. Le Omni
lecteur-écrivain serrure utilise "slim" principes (filage bit-manipulations). Comme la Fenêtre est propre, il ne supporte pas l'escalade du verrouillage. Je l'ai testé, et il ne semble pas 151970920 "to lockup crash or deadlock.
à la fin j'ai utilisé une situation de repli. Le plus générique des interfaces génériques pour soutenir lire-écrire" concepts:
IReaderWriterLock = interface
['{6C4150D0-7B13-446D-9D8E-866B66723320}']
procedure BeginRead;
procedure EndRead;
procedure BeginWrite;
procedure EndWrite;
end;
et ensuite nous décidons à l'exécution quelle implémentation utiliser. Si nous sommes sur Windows Vista ou nouveau, Utilisez le propre SlimReaderWriter
de Window, sinon repli sur Omni
version:
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;
Note : tout code est publié dans le domaine public. Aucune attribution n'est requise.
le Delphi TMultiReadExclusiveWriteSynchronizer
est très sophistiqué - il peut être acquis de façon récursive et vous pouvez mettre à jour de Read
à Write
.
Cela a un coût, qui dans ce cas signifie la gestion d'un seau d'état partagé par thread. Comme le fil de Windows-mécanique locale (accessible via threadvar
) est trop simpliste pour cela (pas capable de faire face à plusieurs instances MREWS), il est fait d'une manière plutôt inefficace – voir les sources RTL ou JCL – les les implémentations sont assez similaires, partageant de mauvaises performances et le risque de mise à jour-impasse.
tout d'abord, assurez – vous que vous avez vraiment besoin de la fonctionnalité MREWS-je suppose, en fonction de la taille proportionnelle de verrouillage en hauteur à la charge de travail, vous serez beaucoup mieux avec un TCriticalSection
.
si vous en avez vraiment-vraiment besoin, allez avec L'implémentation Delphi et faites attention à la possibilité de déverrouillage caché dans " 151950920 – - voir sa documentation et sa valeur de retour sens.
il est possible d'implémenter un Vista-like SRW
en utilisant les fonctions Interlocked
ou l'assemblage en ligne, mais cela ne vaut pas la peine dans la plupart des cas.
la JCL a un MREWS qui est une implémentation différente qui pourrait fonctionner pour vous. Je ne sais pas quelle version de windows il nécessite.
http://wiki.delphi-jedi.org/wiki/JCL_Help:TJclMultiReadExclusiveWrite
http://wiki.delphi-jedi.org/index.php?title=JEDI_Code_Library
essayez ça? Il peut être utilisé comme variable normale:
type myclass=class
Lock:TOBRWLock;
function ReadIt:Integer;
procedure WriteIt(A:Integer);
end;
function ReadIt:Integer;
begin;
Lock.LockRead;
Result:=GetVal;
Lock.UnLockRead;
end;
Il ya beaucoup d'espace pour l'amélioration et vous pouvez construire à partir d'ici des variétés qui favorisent la lecture ci-dessus écrire ou tout simplement agir différemment en fonction des besoins.
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;