Bonjour,
Je cherche des pistes d'optimisations dans le projet de l'équipe dont je fais parti.
L'un des sujets sont les compteurs purs et compteurs type verrou
Les compteurs purs sont systématiquement incrémentés, leur lecture n'a pas d'impact particulier.
Un test multi-thread a démontré que la non protection de l'incrément était très impactant
Ce même test a démontré les performances de AtomicIncrement (un alias pour InterlockedIncrement) et sa pertinence de protection.
Par exemple 16 threads incrémentant de 1 une même variable Integer 250 000 fois, soit un total de 4 000 000 à la fin, c'est à tout juste 500 000 sans protection.
Version Delphi Seatle donc InterlockedIncrement est désormais en ASM comme sous Kylix sans utiliser les API Windows
J'utilise le couple InterlockedIncrement et InterlockedDecrement depuis 2004 lorsque j'ai découvert le TInterfacedObject et le modèle COM (j'avais même joué avec TClassInstancing et TThreadingModel)
Je l'utilise toujours de la même façon, Increment dont je n'ai que faire de la valeur et surtout Decrement où je fais une action sur Zéro, généralement une libération ou le signal d'une fin des traitements parallélisés.
Je ne l'ai encore pas utilisé en deux temps comme ces "compteurs type verrou" où un code pose son verrou et un autre code lit la valeur.
les compteurs type verrou, ce sont principalement des coupe-circuits, typiquement une tache n'est pas effectuée si d'autres ne sont pas en cours.
exemple "compteurs type verrou"
Pose du verrou
1 2 3 4 5 6 7
| try
Context.RunningCount := Context.RunningCount + 1 ;
// Code Traitement couteux en temps
// ...
finally
Context.RunningCount := Context.RunningCount - 1 ;
end ; |
Utilisation du Verrou
1 2 3 4
|
DoAction := Context.Chose and Context.Machin and (Context.RunningCount = 0);
if DoAction then
// Code potentiellement conflictuel avec le Traitement couteux en temps |
Déclaration du Verrou
property RunningCount: Integer read GetRunningCount write SetRunningCount;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function TContext.GetRunningCount: Integer;
begin
FCriticalSection.Enter('Context_GetRunningCount');
try
result := FRunningCount;
finally
FCriticalSection.Release;
end;
end;
procedure TLoginSession.SetRunningCount(AValue: Integer);
begin
FCriticalSection.Enter('Context_SetRunningCount');
try
FRunningCount := AValue;
finally
FCriticalSection.Release;
end;
end; |
En réalité cette Section Critique n'apporte pas de protection car le +1 est en dehors de la protection
Par exemple 16 threads incrémentant de 1 une même variable Integer 250 000 fois, soit un total de 4 000 000 à la fin, c'est à peine 600 000 avec cette protection incomplète.
Voici le code que je propose à mon responsable pour les compteurs verrous, il en sera de même pour les compteurs purs mais Inc uniquement.
Pose du verrou
1 2 3 4 5 6 7
| try
Context.IncRunningCount();
// Code Traitement couteux en temps
// ...
finally
Context.DecRunningCount();
end ; |
Utilisation du Verrou inchangée
Déclaration du Verrou
property RunningCount: Integer read GetRunningCount;
Nouvelles fonctions puisque le compteur est en lecture seule désormais
1 2 3 4 5 6 7 8 9
| function TContext.IncRunningCount(): Integer;
begin
Result := AtomicIncrement(FRunningCount);
end;
function TContext.DecRunningCount(): Integer;
begin
Result := AtomicDecrement(FRunningCount);
end; |
et je m'interroge pour GetRunningCount et c'est la question de ce post
InterlockedCompare n'existe pas, sur Stackoverflow et d'autres forums anglophones, je n'ai pas trouvé de réponse satisfaisante sur l'inutilité de cette fonction.
1 2 3 4
| function TContext.GetRunningCount: Integer;
begin
Result := FRunningCount;
end; |
Ou
1 2 3 4
| function TContext.GetRunningCount: Integer;
begin
Result := AtomicCmpExchange(FRunningCount, 0, 0);
end; |
AtomicCmpExchange modifie FRunningCount si Comparand (le second 0) est égale à FRunningCount pour mettre la valeur NewValue (le premier 0)
Le retour de AtomicCmpExchange c'est la dernière valeur de FRunningCount avant l'échange
Donc soit différent de Zéro en cas d'échec de l'échange avec comparaison à Zéro
Et finalement égale à Zéro en cas de réussite de l'échange avec comparaison à Zéro
Deux questions :
- AtomicCompare est inexistant ! Pourquoi ?
- AtomicCmpExchange est-il utile ou une hérésie pour une protection de lecture ?
Partager