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

Trackback URI | Comments RSS

Leave a reply