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 :

enter image description here

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

21
demandé sur Community 2012-04-30 07:49:50

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;
6
répondu Edwin Yip 2017-05-23 10:29:57

À 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.

3
répondu Ian Boyd 2016-01-15 02:07:52

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.

2
répondu Viktor Svub 2012-10-04 20:41:18

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

0
répondu IvoTops 2012-06-26 15:59:53

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;
0
répondu AntonE 2014-01-20 11:28:48