Archive for the '.NET' Category

Resharper 4: la beta est disponible.

Julien on mai 22nd 2008

Après une longue période en EAP (Early Access program), la beta de reshaper 4 est enfin disponible!
(a priori la stabilité à l'air au rendez-vous, du moins je n'ai pas eu de problème significatif pour le moment).

On trouvera notamment au menu du jour le support du C# 3 et de Linq, de nouveaux refactorings, de nouvelles suggestions, etc. En résumé, que du bon si vous travaillez déjà sous Visual Studio 2008 et .NET 3.5.

Pour ceux qui ne connaissent pas Resharper, je vous suggère vivement de regarder ce site: 31 days of resharper et de télécharger la version d'essai. Je vous garanti que l'essayer c'est l'adopter!

Filed in .NET | 2 responses so far

Comment vérifier qu’une chaine de caractère est vide de façon optimale?

Julien on avr 25th 2008

J'ai eu l'occasion de m'apercevoir qu'il y avait un certain besoin autour de trucs et astuces sur .NET. Je vais donc dorénavant essayer de poster ce qui me passera par la tête :). A noter que ces billets ont vocation à être court et couvrir des points très précis!

Comment vérifier qu'une chaine de caractère est vide de façon optimale?

Il existe plusieurs façon de faire la comparaison:
1) myString == ""
2) myString == String.Empty
3) myString.Length == 0
4) String.IsNullOrEmpty(myString)

La façon la plus performante est la solution 3, myString.Lengh == 0, à condition d'être sur que la chaine de caractère n'est pas nulle. Si la chaine de caractère peut potentiellement être nulle, String.IsNullOrEmpty(myString) est idéal. A noter d'ailleurs que String.IsNullOrEmpty(myString) utilise lui même string.Length == 0. En voici l'implémentation en guise de référence:

  1. public static bool IsNullOrEmpty(string value)
  2. {
  3. if (value != null)
  4. {
  5. return (value.Length == 0);
  6. }
  7. return true;
  8. }

Filed in .NET, Trucs et Astuces | No responses yet

Impact du mot cléf readonly sur les performances

Julien on avr 22nd 2008

Un collègue de travail m'a dit que le mot clef readonly permettait d'optimiser les accès mémoire et par conséquent l'accès se faisait plus rapidement qu'un membre classique (sans le mot clef readonly). Je dois avouer que cette affirmation m'a un peu surpris. J'aurai d'abord parié sur des performances identiques avec ou sans ce mot clef (Ce serait juste une vérification du compileur, qui ne modifie en rien l'exécution); puis, si cette première théorie s'avérait fausse, sur un accès mémoire plus lent (La CLR pourrait faire des vérifications lorsqu'elle essaye d'accéder au membre). Voyons donc si je me trompe!

Malheureusement pour moi, je n'ai trouvé aucune source pour confirmer ou non mes théories... J'ai donc décidé de réaliser mon propre micro-benchmark.

J'ai mesuré le temps nécessaire pour exécuter 3 principaux scénarios, avec un membre en lecture seule (readonly) puis avec un champs en lecture écriture (donc sans readonly):
- Scénario 1: Création d'un objet
- Scénario 2: Accès à un membre d'un objet
- Scénario 3: Création d'un objet et accès à un membre

j'ai exécuté ce code 1 000 000 000 de fois pour chaque scénario, et j'ai lancé chaque scénario 10 fois de suite sur 4 PC différents afin d'avoir une moyenne représentative. Pour chaque test, j'ai exclus le résultat le plus rapide et le plus lent.
Le code de mes tests (en C# et .NET 2.0) est disponible ici (Le code est dupliqué pour chaque test. Je sais que c'est horrible, toutes mes excuses :)), et les résultats sont .

Chaque test ressemble à cela (avec quelques modifications évidemment):

  1. _streamWriter.WriteLine();
  2. _streamWriter.WriteLine("Obj creation & access in loop");
  3. for (int j = 0; j < _numberOfTestExecutions; j++)
  4. {
  5. Poco poco = new Poco();
  6.  
  7. Stopwatch watch = new Stopwatch();
  8. watch.Start();
  9. for (int i = 0; i < _numberOfIterationPerTest; i++)
  10. {
  11. ReadOnlyField objReadOnlyField = new ReadOnlyField(poco);
  12. Poco myCopyOfPoco = objReadOnlyField.Poco;
  13. }
  14. watch.Stop();
  15. _streamWriter.Write(watch.ElapsedMilliseconds + ";");
  16.  
  17. watch.Reset();
  18. watch.Start();
  19. for (int i = 0; i < _numberOfIterationPerTest; i++)
  20. {
  21. ReadWriteField objReadWriteField = new ReadWriteField(poco);
  22. Poco myCopyOfPoco = objReadOnlyField.Poco;
  23. }
  24. watch.Stop();
  25. _streamWriter.WriteLine(watch.ElapsedMilliseconds);
  26. }

Voici les chiffres bruts:

Scénario 1: Création d'un objet
- lecture seule: 25172ms
- lecture écriture: 24983ms
La création d'un objet avec un membre en lecture seule est en moyenne 0.76% plus lent.

Scénario 2: Accès à un membre d'un objet
- lecture seule: 6437ms
- lecture écriture: 6449ms
L'accès à un membre en lecture seule est 0.19% plus rapide

Scénario 3: Création d'un objet et accès à un membre
- lecture seule: 19929ms
- lecture écriture: 19761ms
La création avec un membre en lecture seule puis l'accès au membre est en moyenne 0.85% plus lent

Cependant, Il est possible de voir des différences plus significatives en regardant les résultats pour chaque PC. Par exemple, sur le premier PC sur lequel j'ai fait le test (un core 2 duo, 2,8Ghz, DDR2 800Mhz), J'ai les résultats suivants:

- Scénario 1: Création d'un objet
La création d'un objet avec un membre en lecture seule est en moyenne 5.72% plus rapide qu'avec un membre en lecture écriture.
- Scenario 2: Accès à un membre d'un objet
L'accès à un membre en lecture seule est 0.94% plus rapide.
- Scenario 3: Création d'un objet et accès à un membre
La création avec un membre en lecture seule puis l'accès au membre est en moyenne 2.28% plus rapide.

Et sur un portable (centrino 1,7ghz, DDR):
- Scenario 1: Création d'un objet
La création d'un objet avec un membre en lecture seule est en moyenne 5.72% plus lent qu'avec un membre en lecture écriture.
- Scenario 2: Acces à un membre d'un objet
L'accès à un membre en lecture seule est 0.21% plus rapide.
- Scenario 3: Création d'un objet et accès à un membre
La création avec un membre en lecture seule puis l'accès au membre est en moyenne 3.01% plus lent.

Mes (propres) conclusions:
Il semblerait qu'il y ait une différence de performances non négligeable lorsque l'on utilise le mot clef readonly. Le problème est que cet impact est très dépendant du matériel sur lequel le logiciel tourne. Sur un PC avec du matériel décent, accéder à un membre en lecture seule est entre 1% et 2.5% plus rapide qu'accéder à un champs en lecture écriture. Quand à la création d'un objet, elle peut être jusqu'à 6% plus rapide. Sur du matériel plus ancien, les résultats sont inversés.
Cependant, les tests ont été exécutés avec une classe très simple, gardez donc en mémoire que dans le réalité, les différences constatées pour la création d'objets seraient probablement beaucoup moins importantes avec des classes qui ont plus de membres.

A ce stade, j'exclurai donc d'utiliser le mot clef readonly à des fins d'optimisation, sauf peut être dans des cas de figures très précis, et seulement après avoir vérifié qu'il y a effectivement un gain de performance sur les machines qui feront tourner le logiciel. A moins que votre application soit:
- installé que sur un nombre limité de machines, dans un environnement contrôlé et homogène
- pseudo temps réel ou extrêmement sensible aux performances
je ne pense pas que vous devriez considérer l'impact du mot clef readonly sur les performances. Il y a probablement des centaines d'optimisations qui seront plus efficaces!

Enfin, si vous avez constaté des résultats différents, n'hésitez pas à me le faire savoir et j'éditerai ce billet en conséquence :).
Je suis particulièrement intéressé par des benchmark avec des processeurs Xeon (malheureusement, je n'en ai pas sous la main).

Filed in .NET | No responses yet

AutoResetEvent vs ManualResetEvent: Attention!

Julien on avr 7th 2008

J'ai effectué quelques tests de montée en charge il y a quelques jours sur le composant sur lequel je travaille. Malheureusement, je me suis rapidement rendu compte que les performances étaient simplement misérables... Le soft était à peine capable de traiter 60 messages par secondes, ce qui était: 1) mauvais et 2) surprenant! (En résumé, c'est une petite brique logiciel qui fait une sorte de transformation/redirection/cache de messages en temps réel)

J'ai donc passé quelques minutes dans DotTrace afin d'avoir un aperçu de l'exécution. J'ai rapidement trouvé que dans le thread qui observait la queue et envoyait les messages, j'avais utilisé une instance de ManualResetEvent à la place de AutoResetEvent.

Si vous ne les avez jamais utilisé, ces 2 classes permettent d'envoyer des signaux entre 2 threads. De cette façon, le thread qui observe la queue ne consomme pas de ressources bêtement tant que le thread qui remplit la queue n'envoie pas un signal pour prévenir qu'il y a quelque chose à récupérer. Par exemple, le thread 1 attendra que la queue soit remplie de la façon suivante:

  1.  
  2. private AutoResetEvent mySignal = new AutoResetEvent(false);
  3.  
  4. private void MonitorQueue()
  5. {
  6. while(!monitorQueue)
  7. {
  8. mySignal.WaitOne();
  9. GetItemsInTheQueueAndDoStuff();
  10. }
  11. }

Et le thread 2 injectera des données avec:

  1.  
  2. public void EnqueueItem(Item myItem)
  3. {
  4. InsertInQueue(myItem);
  5. mySignal.Set();
  6. }
  7.  

Comme je l'ai dis, mon problème venait du fait que j'avais utilisé par erreur une instance de ManualResetEvent à la place de AutoResetEvent. Ces 2 classes sont quasiment identiques, excepté que lorsque vous utilisez ManualResetEvent, vous devez reseter le signal manuellement avec mySignal.Reset(). Dans mon cas, à la place de bloquer sur mySignal.WaitOne(), le code bouclait en permanence et consommait une quantité de CPU très importante.

J'ai corrigé le problème et relancé mon test de performance: je suis maintenant à 5000 messages par secondes et à moins de 40% de CPU. Bien mieux!

Filed in .NET | No responses yet

lock(this): A ne pas faire!

Julien on avr 5th 2008

J'ai vu ce genre de choses dans plusieurs bouts de code récemment:

  1. lock(this)
  2. {
  3. // Do stuff...
  4. }

Même si cela fonctionne, c'est une très mauvaise idée! Vous ne devriez jamais (ou du moins je n'ai aucune bonne raison en tête) faire un lock sur un type public, ce qui inclue évidemment le "this". Il y a une raison très simple à cela: vous n'avez aucun moyen de maitriser ce que le code appelant va essayer de faire.

Prenons un exemple simple. Dans le code suivant, nous avons une class qui utilise un lock sur "this" (On l'appellera ClassThatLocksItself). Nous avons aussi une seconde class (CallerClass) qui va appeler la première. CallerClass va alors utiliser un lock sur l'instance de ClassThatLocksItself lors de cet appel.

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ClassThatLocksItself myObj = new ClassThatLocksItself();
  6.  
  7. CallerClass caller = new CallerClass(myObj);
  8. caller.LockTheObjectInAThread();
  9. Thread.Sleep(500);
  10.  
  11. myObj.LockMe();
  12. }
  13. }
  14.  
  15. class CallerClass
  16. {
  17. private ClassThatLocksItself _myObj;
  18. public CallerClass(ClassThatLocksItself myObj)
  19. {
  20. _myObj = myObj;
  21. }
  22.  
  23. public void LockTheObjectInAThread()
  24. {
  25. ThreadPool.QueueUserWorkItem(LockTheObject);
  26. }
  27.  
  28. public void LockTheObject(object state)
  29. {
  30. Console.WriteLine("Acquiring lock on the object");
  31. lock (_myObj)
  32. {
  33. Thread.Sleep(100000); // Do a long computation
  34. }
  35. Console.WriteLine("Releasing lock on the object");
  36. }
  37. }
  38.  
  39. public class ClassThatLocksItself
  40. {
  41. public void LockMe()
  42. {
  43. Console.WriteLine("ClassThatLocksItself -- Trying to acquire lock on this");
  44. lock (this)
  45. {
  46. Console.WriteLine("ClassThatLocksItself -- lock on this acquired");
  47. }
  48. Console.WriteLine("ClassThatLocksItself -- lock on this released");
  49. }

Si vous essayez d'exécuter ce code, vous remarquerez que Console.WriteLine("ClassThatLocksItself -- lock on this acquired"); est seulement executé lorsque la fonction LockTheObject() retourne (dans notre cas, après 100 secondes!). En utilisant lock(this), vous êtes devenu dépendant de facteurs externes inconnus (dans notre exemple, CallerClass a décidé d'utiliser l'instance de CLassThatLocksItself pour la synchronisation, ce que ClassThatLocksItself n'avait pas moyen de savoir). La situation est même pire pour les développeurs qui veulent réutiliser votre ClassThatLocksItself: a moi de parcourir la source de la classe, ils n'ont aucun moyen de savoir qu'il peut y avoir de grave problèmes de synchronisation.

Maintenant, essayez de deviner ce qui va se passer si l'on modifie l'implémentation de LockTheObject() par la suivante:

  1. public void LockTheObject(object state)
  2. {
  3. Console.WriteLine("Acquiring lock on the object");
  4. lock (_myObj)
  5. {
  6. _myObj.LockMe(); // will not lock!
  7. }
  8. Console.WriteLine("Releasing lock on the object");
  9. }

Un certain nombre d'entre vous devrait parier sur le deadlock je suppose. Mais non! En fait, la CLR est assez intélligente pour détecter que vous avez déjà acquis le lock sur le même objet lorsque LockMe execute lock(this). N'oubliez pas, nous sommes dans le même thread! Cela rend lock(this) très subtile: vous n'aurez des problèmes que dans des cas très spécifiques!.

Filed in .NET | No responses yet