<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Codingly &#187; Optimisation</title>
	<atom:link href="http://codingly.com/tag/optimisation/feed/" rel="self" type="application/rss+xml" />
	<link>http://codingly.com</link>
	<description>Par Romain Verdier</description>
	<lastBuildDate>Fri, 27 Jan 2012 13:42:50 +0000</lastBuildDate>
	<language>fr</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='codingly.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://0.gravatar.com/blavatar/62c090a2ec42be744d871177bd874854?s=96&#038;d=http%3A%2F%2Fs2.wp.com%2Fi%2Fbuttonw-com.png</url>
		<title>Codingly &#187; Optimisation</title>
		<link>http://codingly.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://codingly.com/osd.xml" title="Codingly" />
	<atom:link rel='hub' href='http://codingly.com/?pushpress=hub'/>
		<item>
		<title>Si les types étaient des animaux, TypedReference serait un ornithorynque</title>
		<link>http://codingly.com/2009/01/15/si-les-types-etaient-des-animaux-typedreference-serait-un-ornithorynque/</link>
		<comments>http://codingly.com/2009/01/15/si-les-types-etaient-des-animaux-typedreference-serait-un-ornithorynque/#comments</comments>
		<pubDate>Thu, 15 Jan 2009 00:08:29 +0000</pubDate>
		<dc:creator>Romain Verdier</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[CIL]]></category>
		<category><![CDATA[Optimisation]]></category>

		<guid isPermaLink="false">http://codingly.com/?p=535</guid>
		<description><![CDATA[Devinette : C&#8217;est un type valeur, mais on ne peut pas le caster en object. Il est impossible d&#8217;en déclarer des tableaux. On ne peut l&#8217;utiliser que pour typer les paramètres de méthodes et les variables locales. Il existe 4 mots clés non documentés en C# qui lui sont directement reliés, et autant d&#8217;opcodes en [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&amp;blog=3510695&amp;post=535&amp;subd=romainverdier&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Devinette : C&#8217;est un type valeur, mais on ne peut pas le caster en <code>object</code>. Il est impossible d&#8217;en déclarer des tableaux. On ne peut l&#8217;utiliser que pour typer les paramètres de méthodes et les variables locales. Il existe 4 mots clés non documentés en C# qui lui sont directement reliés, et autant d&#8217;opcodes en CIL. Il permet notamment le support des varargs, et exactement 8 personnes dans le monde se sont souciées plus de 5 min de son existence.</p>
<p>Je veux parler, bien évidemment, de <code><a href="http://www.urbandictionary.com/define.php?term=PITA">TypedReference</a></code>. Je vous propose de découvrir ce type à partir d&#8217;un exemple rigolo.<br />
<span id="more-535"></span></p>
<h3>Méthodes <em>varargs</em> et interopérabilité</h3>
<p>En C#, lorsqu&#8217;on veut définir une méthode à nombre de paramètres variables, on utilise la syntaxe suivante :</p>
<p><pre class="brush: csharp;">
public static double Average(params int[] values)
{
    double sum = 0;
    foreach (var i in values)
        sum += i;
    return sum / values.Length;
}
</pre></p>
<p>Notez le mot clé <code>params</code> qui permet l&#8217;appel de la méthode en passant un tableau en paramètre, ou en listant plus librement les arguments :</p>
<p><pre class="brush: csharp;">
var parameters = new int[]{45, 68, 97, 45};
ManagedClass.Average (parameters)
// ou bien
ManagedClass.Average (45, 68, 97, 45)
</pre></p>
<p>En <a href="http://en.wikipedia.org/wiki/C%2B%2B/CLI">C++ / CLI</a>, la syntaxe est un peu différente mais le principe est le même :</p>
<p><pre class="brush: cpp;">
double CodinglyInterop::CppCliLibrary::average(... array&lt;int,1&gt; ^values)
{
	double sum = 0;
	for each (int i  in values)
		sum += i;
	return sum/values-&gt;Length;
}
</pre></p>
<p>C&#8217;est pareil, en plus moche. On utilise &#8220;<code>...</code>&#8221; au lieu de &#8220;<code>params</code>&#8220;, et l&#8217;appelant peut passer un tableau ou une liste d&#8217;arguments :</p>
<p><pre class="brush: csharp;">
var parameters = new int[]{45, 68, 97, 45};
CppCliLibrary.average(parameters)
// ou bien
CppCliLibrary.average(45, 68, 97, 45)
</pre></p>
<p>Notez que l&#8217;interopérabilité entre C# et C++/CLI est native : les deux langages sont managés.</p>
<p>Et en C++ pas CLI, qu&#8217;est-ce que donnent les fonctions à nombre variable d&#8217;arguments ?</p>
<p>Bah c&#8217;est encore plus moche, et plus contraignant. Rappelez-vous :</p>
<p><pre class="brush: cpp;">
extern &quot;C&quot; __declspec(dllexport) double average(int n, ...)
{
	double sum = 0;
	va_list args;
	va_start(args, n);
	for(int i=0 ;i &lt; n; i++)
		sum += va_arg(args, int);
	va_end(args);
	return sum/n;
}
</pre></p>
<p>La principale différence est finalement assez subtile on n&#8217;utilise pas explicitement un tableau, mais on fournit un pointeur vers le début de la liste d&#8217;arguments. C&#8217;est à la méthode de se débrouiller (avec des macros) pour itérer, caster et s&#8217;arrêter lorsqu&#8217;il le faut. Généralement, un autre paramètre nommé de la méthode contient les infos nécessaires à cette opération. Dans l&#8217;exemple précédent, &#8220;<code>n</code>&#8221; est utilisé pour passer directement le nombre d&#8217;arguments de la liste. Dans <code>printf</code>, c&#8217;est la <em>format string</em> qui permet à la fonction de savoir combien d&#8217;arguments elle doit lire.</p>
<p>La question rigolote, puisque nous sommes dans le contexte d&#8217;un exemple rigolo, est la suivante :</p>
<p>Mais comment appeler les fonctions natives de ce genre depuis C# ? En utilisant <code><a href="http://fr.wikipedia.org/wiki/P/Invoke">pinvoke</a></code>, probablement, mais plus précisément ?</p>
<p>Réponse : En pleurant. On est obligé de déclarer explicitement les imports pour chacune des utilisations que l&#8217;on va faire de la fonction dans notre code managé. En gros :</p>
<p><pre class="brush: csharp;">
[DllImport (&quot;NativeLibrary.dll&quot;)]
public static extern double average (int n, int i1);

[DllImport (&quot;NativeLibrary.dll&quot;)]
public static extern double average (int n, int i1, int i2);

[DllImport (&quot;NativeLibrary.dll&quot;)]
public static extern double average (int n, int i1, int i2, int i3);

[DllImport (&quot;NativeLibrary.dll&quot;)]
public static extern double average (int n, int i1, int i2, int i3, int i4);

// Etc.
</pre></p>
<p>What a <a href="http://www.urbandictionary.com/define.php?term=PITA">PITA</a>, comme qui dirait&#8230; Mais c&#8217;est ici que l&#8217;ornithorynque nous sauve la vie. Car on peut écrire :</p>
<p><pre class="brush: csharp;">
[DllImport (&quot;NativeLibrary.dll&quot;, CallingConvention = CallingConvention.Cdecl)]
public static extern double average (int n, __arglist );
</pre></p>
<p>Et appeler la fonction ainsi :</p>
<p><pre class="brush: csharp;">
NativeWrapper.average (4, __arglist (45, 68, 97, 45))
</pre></p>
<p>Hourra, donc. Mais quel est ce mot clé bizarre, <code>__arglist</code>, qui n&#8217;est même pas reconnu par Resharper ? Et bien il s&#8217;agit d&#8217;un des mots clés <em>non documentés</em> du langage, qui permettent principalement de jouer avec les <code>TypedReference</code>. Pour mieux comprendre ce qui se passe, essayons de réécrire en C# la méthode <code>Average</code>, sans utiliser le mot clé <code>params</code>, et en se reposant donc uniquement sur les &#8220;références typées&#8221;. On s&#8217;attaquera ensuite à la définition.</p>
<p><pre class="brush: csharp;">
public class ManagedEvil
{
    public static double Average(__arglist)
    {
        double sum = 0;
        int count = 0;
        var iterator = new ArgIterator(__arglist);
        do
        {
            TypedReference typedReference = iterator.GetNextArg();
            sum += __refvalue( typedReference,int);
            count++;
        } while (iterator.GetRemainingCount() &gt; 0);
        return sum / count;
    }
}
</pre></p>
<p>On note :</p>
<ul>
<li>L&#8217;apparition du type <code>ArgIterator</code>, dont on crée une instance à partir de la liste d&#8217;arguments.</li>
<li>L&#8217;élément courant retourné par l&#8217;itérateur est une référence typée : <code>TypedReference</code>.</li>
<li>Un nouveau mot clé, <code>__refvalue</code>, permet d&#8217;extraire la valeur pointée par la référence.</li>
</ul>
<p>Et tout ça compile, tout ça fonctionne, même si l&#8217;intérêt est limité. </p>
<h3><code>TypedReference</code>, <code>__makeref</code>, <code>__refvalue</code>, <code>__reftype</code></h3>
<p>Arrêtons les expériences pour décortiquer un peu plus sérieusement ce <code>TypedReference</code>. La MSDN définit <strike>très clairement</strike> la <code>TypedReference</code> :</p>
<blockquote><p>Describes objects that contain both a managed pointer to a location and a runtime representation of the type that may be stored at that location.</p></blockquote>
<p>Retenons qu&#8217;il s&#8217;agit d&#8217;une structure contenant : </p>
<ul>
<li>Un pointeur managé vers un espace mémoire</li>
<li>Le type de ce qui est pointé en mémoire</li>
</ul>
<p>Le meilleur endroit pour trouver des infos au sujet de <code>TypedReference</code> est finalement la spécification <a href="http://www.ecma-international.org/publications/standards/Ecma-335.htm">ECMA 335 du CIL</a>. Je vous laisse fouiller, mais sachez que les bases sont notamment posées dans le paragraphe suivant (cf. 8.6.1.3):</p>
<blockquote><p>La signature d&#8217;une référence typée est en fait représentée en tant que type valeur de base, comme les entiers ou nombres à virgule flottante. Dans la bibliothèque de classes du framework, le type est représenté par <code>System.TypedReference</code>, tandis que dans le langage intermédiaire il est désigné par le mot clé <code>typedref</code>. Ce type doit uniquement être utilisé pour les paramètres et les variables locales. Il ne peut ni être boxé ni être utilisé pour typer un champ, un élément de tableau, ou une valeur de retour de fonction.</p></blockquote>
<p>Pour que je ne puisse jamais justifier l&#8217;intérêt de cet article, les gens chez Microsoft ont bien fait attention à exposer ce type le moins possible. Il est pourtant assez utilisé en interne, notamment dans <code>System.Array</code>, <code>System.Threading.Interlocked</code>, les services d&#8217;intérop (<code>InteropServices</code>) ainsi que dans certains overloads cachés utilisant les varargs (<code>String.Concat</code>, <code>Console.WriteLine</code>). A vrai dire, il semblerait que la raison officielle de son existence soit justement le support par le CLR des listes d&#8217;arguments, comme nous l&#8217;avons vu plus tôt. Il existe cependant un autre endroit (parmi d&#8217;autres) où son utilisation peut s&#8217;avérer intéressante : la réflexion via les <code>FieldInfo</code>. Mais avant d&#8217;y venir, regardons plutôt comment utiliser, très mécaniquement, <code>TypedReference</code>. On peut soit utiliser les mots clés interdits, soit &#8211; lorsque c&#8217;est possible &#8211; utiliser les quelques méthodes statiques de la classe <code>TypedReference</code> elle-même.</p>
<p><strong>Création d&#8217;une référence typée</strong> : <code>__makeref</code></p>
<p>Les références typées peuvent être obtenues pour des variables de n&#8217;importe quel type (référence ou valeur) à l&#8217;aide du mot clé <code>__makeref</code>.</p>
<p><pre class="brush: csharp;">
int i = 42;
TypedReference typedReference = __makeref (i);
</pre></p>
<p>Ce mot clé dont l&#8217;usage n&#8217;est pas documenté correspond à l&#8217;instruction <code>mkrefany</code> en CIL. Le code intermédiaire correspondant à l&#8217;exemple précédant est le suivant :</p>
<p><pre class="brush: cpp;">
int i = 42;

L_0000: ldc.i4.s 0x2a	/* push sur la pile d'un int32 = 42						*/
L_0002: stloc.0			/* sauvegarde dans la variable locale i					*/

TypedReference typedReference = __makeref(i);

L_0003: ldloca.s i		/* push sur la pile de l'adresse de i					*/
L_0005: mkrefany int32	/* push sur la pile de la référence typée				*/
L_000a: stloc.1			/* sauvegarde dans la variable locale typedReference	*/
</pre></p>
<p>La méthode statique <code>TypedReference.MakeTypedReference</code> permet aussi de créer des références typées, mais pas directement à partir d&#8217;une variable. Nous verrons un exemple d&#8217;utilisation dans la dernière partie de l&#8217;article.</p>
<p><strong>Récupérer et/ou modifier la valeur</strong> : <code>__refvalue</code></p>
<p>Une fois qu&#8217;on a une référence typée, il est possible de lire et/ou d&#8217;écrire la valeur référencée en utilisant le mot clé <code>__refvalue</code>. Il est visuellement assimilable à un appel de fonction à deux paramètres, dont le premier serait la <code>TypedReference</code>, et le second le type de la valeur référencée. En l&#8217;utilisant à droite d&#8217;une affectation, on lit la valeur, et en l&#8217;utilisant à gauche d&#8217;une affectation, on écrit la valeur. Assez troublant&#8230;</p>
<p><pre class="brush: csharp;">
int i = 42;
TypedReference typedReference = __makeref(i);
int j = __refvalue (typedReference, int);
__refvalue (typedReference, int) = 24;
</pre></p>
<p>Encore une fois, il est possible de mapper son utilisation avec un opcode spécifique, <code>refanyval</code> :</p>
<p><pre class="brush: cpp;">
int i = 42;
TypedReference typedReference = __makeref(i);

L_0000: ldc.i4.s 0x2a	/* push sur la pile d'un int32 = 42						*/
L_0002: stloc.0			/* sauvegarde dans la variable locale i					*/
L_0003: ldloca.s i		/* push sur la pile de l'adresse de i					*/
L_0005: mkrefany int32	/* push sur la pile de la référence typée				*/
L_000a: stloc.1			/* sauvegarde dans la variable locale typedReference	*/

int j = __refvalue (typedReference, int);

L_000b: ldloc.1			/* push sur la pile de la typedReference				*/
L_000c: refanyval int32 /* push sur la pile de l'adresse de la référence        */
L_0011: ldind.i4		/* déréférencement et push sur la pile de la valeur		*/
L_0012: stloc.2			/* sauvegarde dans la variable locale j					*/

__refvalue (typedReference, int) = 24;

L_0013: ldloc.1			/* push sur la pile de la typedReference				*/
L_0014: refanyval int32 /* push sur la pile de l'adresse de la référence        */ 
L_0019: ldc.i4.s 0x18	/* push sur la pile d'un int32 = 24						*/
L_001b: stind.i4		/* sauvegarde de la valeur à l'adresse de la référence  */
</pre></p>
<p>La méthode <code>TypedReference.ToObject</code> permet aussi de déréférencer la <code>TypedReference</code> et d&#8217;obtenir sa valeur, boxée.</p>
<p><strong>Récupérer le type</strong> : <code>__reftype</code></p>
<p>Enfin, il est possible de récupérer l&#8217;information de type associée à la <code>TypeReference</code>, en utilisant un dernier mot clé <code>__reftype</code>.</p>
<p><pre class="brush: csharp;">
int i = 42;
TypedReference typedReference = __makeref(i);
Type type = __reftype(typedReference);
</pre></p>
<p>Là encore, on peut faire correspondre <code>__reftype</code> à l&#8217;opcode <code>refanytype</code> :</p>
<p><pre class="brush: cpp;">
int i = 42;
TypedReference typedReference = __makeref(i);

L_0000: ldc.i4.s 0x2a	/* push sur la pile d'un int32 = 42						*/
L_0002: stloc.0			/* sauvegarde dans la variable locale i					*/
L_0003: ldloca.s i		/* push sur la pile de l'adresse de i					*/
L_0005: mkrefany int32	/* push sur la pile de la référence typée				*/
L_000a: stloc.1			/* sauvegarde dans la variable locale typedReference	*/

Type type = __reftype(typedReference);

L_000b: ldloc.1			/* push sur la pile de la typedReference				*/
L_000c: refanytype		/* push sur la pile du type token de la référence		*/
L_000e: call class Type::GetTypeFromHandle(RuntimeTypeHandle)
L_0013: stloc.2			/* récupération et sauvegarde du Type à partir du token */
</pre></p>
<p>Notons que la méthode statique <code>TypedReference.GetTargetType</code> est l&#8217;équivalent autorisé de <code>__reftype</code>, et qu&#8217;il existe aussi la méthode <code>TypedReference.TargetTypeToken</code> qui retourne le handle du type sous la forme d&#8217;un <code>RuntimeTypeHandle</code>. La version light, en somme. </p>
<p>Rien de très impressionnant ; d&#8217;ailleurs vous pouvez retourner sur youtube car la suite n&#8217;est pas mieux. Mais je persiste !</p>
<h3>Un autre exemple : <code>GetValueDirect</code> et <code>SetValueDirect</code></h3>
<p>Je vous disais tout à l&#8217;heure que j&#8217;avais trouvé un exemple d&#8217;utilisation dans lequel on pouvait faire intervenir les <code>TypedReference</code>. Il s&#8217;agit de la lecture écriture des champs par réflexion, et plus particulièrement des champs de type valeur. Et encore plus particulièrement lorsqu&#8217;ils sont imbriqués.</p>
<p>Prenons pour exemple ce modèle simpliste :</p>
<p><pre class="brush: csharp;">
public struct Person
{
	public Address Address;
}

public struct Address
{
	public City City;
}

public struct City
{
	public int ZipCode;
}
</pre></p>
<p>Il est important de bien noter que <code>Person</code>, <code>Address</code>, <code>City</code> et <code>ZipCode</code> sont des value types. Si on veut inspecter une instance de <code>Person</code> par réflexion, jusqu&#8217;à lire le <code>ZipCode</code> de son adresse, on va écrire quelque chose comme :</p>
<p><pre class="brush: csharp;">
// On a une instance de Person
var p = new Person();
p.Address.City.ZipCode = 75000;

// On récupère les FieldInfo
var addressField = typeof(Person).GetField(&quot;Address&quot;);
var cityField = typeof(Address).GetField(&quot;City&quot;);
var zipCodeField = typeof(City).GetField(&quot;ZipCode&quot;);

// On chaine les appels à FieldInfo.GetValue pour inspecter la Person
// et lire la valeur du ZipCode
var zipCode =  (int)zipCodeField.GetValue(cityField.GetValue(addressField.GetValue(p)));
</pre></p>
<p>Ca fonctionne, mais le boxing a tué mon hourra. <code>GetValue</code> prend un <code>object</code> en argument, et retourne un <code>object</code>, alors qu&#8217;on travaille ici sur des types valeurs&#8230; En lisant la dernière ligne de droite à gauche :</p>
<ul>
<li>p va être boxé pour être passé en paramètre à <code>GetValue</code></li>
<li><code>addressField.GetValue</code> va boxer la valeur du champ <code>Address</code> pour le retourner sous la forme d&#8217;un <code>object</code></li>
<li><code>cityField.GetValue</code> va boxer la valeur du champ <code>City</code> pour le retourner sous la forme d&#8217;un <code>object</code></li>
<li><code>zipCodeField.GetValue</code> va boxer la valeur du champ <code>ZipCode</code> pour le retourner sous la forme d&#8217;un <code>object</code></li>
<li>Le cast en <code>int</code> effectue l&#8217;ultime et nécessaire unboxing</li>
</ul>
<p>Pas terrible&#8230;</p>
<p>Heureusement, il existe sur <code>FieldInfo</code> la méthode <code>GetValueDirect</code>, qui prend en paramètre une <code>TypedReference</code> !</p>
<p>Si on peut directement récupérer une référence typée sur le <code>ZipCode</code>, on peut éviter quelques emboitages :</p>
<p><pre class="brush: csharp;">
var p = new Person();
p.Address.City.ZipCode = 75000;
FieldInfo zipCodeField = typeof(City).GetField(&quot;ZipCode&quot;);
var zipCode = (int)zipCodeField.GetValueDirect(__makeref (p.Address.City)); 
</pre></p>
<p>Seulement, on change les contraintes, car la création de la référence typée de cette façon implique alors que l&#8217;on connaisse <code>Address</code> et <code>City</code> au design time. Qu&#8217;à cela ne tienne : il existe la méthode statique <code>TypedReference.MakeTypedReference</code> évoquée plus tôt, qui permet de construire une référence typée à partir d&#8217;une target et d&#8217;un tableau de <code>FieldInfo</code> correspondant à l&#8217;inspection désirée de l&#8217;espace mémoire réservé par <code>p</code> :</p>
<p><pre class="brush: csharp;">
var p = new Person ();
p.Address.City.ZipCode = 75000;
var addressField = typeof (Person).GetField (&quot;Address&quot;);
var cityField = typeof (Address).GetField (&quot;City&quot;);
var zipCodeField = typeof (City).GetField (&quot;ZipCode&quot;);

TypedReference r = TypedReference.MakeTypedReference (p, new[] {addressField, cityField});
var zipCode = (int) zipCodeField.GetValueDirect (r);
</pre></p>
<p>Dans ce cas précis, les performances mesurées sont environ <strong>4 fois meilleures</strong> en utilisant <code>GetValueDirect</code> à la place de <code>GetValue</code>. Hourra.</p>
<p>Quant à l&#8217;écriture, nous n&#8217;avons même pas le luxe du choix, puisque chainer les <code>SetValue</code> ne peut en aucun cas modifier la personne, cette fonction retournant à chaque fois une <em>copie</em> des valeurs&#8230; Il faut donc nécessairement utiliser <code>SetValueDirect</code> et une <code>TypedReference</code>.</p>
<h3>Conclusion</h3>
<p>La conclusion, que je vous dois brève :</p>
<p><strong>Ce qui n&#8217;est pas documenté ne doit pas être utilisé</strong>. En gros, tout ce qui est lié aux arglists est à oublier. En revanche, la création et la manipulation de <code>TypedReference</code> sans passer par tous les __mots __clés __moches n&#8217;est pas réprouvée. Notez simplement que <code>TypedReference</code> n&#8217;est pas <em>CLS Compliant</em>.</p>
<br />Publié dans Articles Tagged: .NET, C#, CIL, Optimisation <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/romainverdier.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/romainverdier.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/romainverdier.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/romainverdier.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/romainverdier.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/romainverdier.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/romainverdier.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/romainverdier.wordpress.com/535/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&amp;blog=3510695&amp;post=535&amp;subd=romainverdier&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://codingly.com/2009/01/15/si-les-types-etaient-des-animaux-typedreference-serait-un-ornithorynque/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/cb0ee4bde49708f4be24a02a5d59e52e?s=96&#38;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">Romain</media:title>
		</media:content>
	</item>
		<item>
		<title>Continuer l&#8217;optimisation avec la Lightweight Code Generation (LCG)</title>
		<link>http://codingly.com/2008/05/06/continuer-loptimisation-avec-la-lightweight-code-generation-lcg/</link>
		<comments>http://codingly.com/2008/05/06/continuer-loptimisation-avec-la-lightweight-code-generation-lcg/#comments</comments>
		<pubDate>Tue, 06 May 2008 13:46:33 +0000</pubDate>
		<dc:creator>Romain Verdier</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[CIL]]></category>
		<category><![CDATA[LCG]]></category>
		<category><![CDATA[Optimisation]]></category>
		<category><![CDATA[Reflection]]></category>

		<guid isPermaLink="false">http://romainverdier.wordpress.com/?p=34</guid>
		<description><![CDATA[Cet article est un complément du précédent. Vous pouviez y lire dans la conclusion : Nous nous sommes contentés d’évoquer la solution d’optimisation impliquant la génération de code IL. Dans le contexte de la problématique discutée, elle n’offrait aucun avantage par rapport à celle que nous avons exposée. Pire, elle imposait une étape inutile. Toutefois, [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&amp;blog=3510695&amp;post=34&amp;subd=romainverdier&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Cet article est un complément du <a href="http://codingly.com/2008/05/02/optimisation-des-invocations-dynamiques-de-methodes-en-c/">précédent</a>. Vous pouviez y lire dans la conclusion :</p>
<blockquote><p>Nous nous sommes contentés d’évoquer la solution d’optimisation impliquant la génération de code IL. Dans le contexte de la problématique discutée, elle n’offrait aucun avantage par rapport à celle que nous avons exposée. Pire, elle imposait une étape inutile. Toutefois, certains besoins plus complexes dépassent le cadre des invocations dynamiques de méthodes et peuvent tout de même être adressés efficacement en recourant à la génération de bytecode.</p></blockquote>
<p>S&#8217;il existe des scénarios dans lesquels le recourt à la <a href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.dynamicmethod.aspx">LCG</a> est inutile voire pénalisant, il n&#8217;y a parfois aucune autre alternative lorsqu&#8217;il s&#8217;agit de mettre en place une solution où les performances sont aussi importantes que la dynamicité.</p>
<p>Vous ne trouverez pas ici un tutorial sur l&#8217;utilisation de <code>Reflection.Emit</code>, mais plutôt un exemple d&#8217;utilisation de cette technique pour répondre de façon optimale à un besoin bien spécifique. Nous essaierons en parallèle de faire ressortir quelques guidelines relatives à l&#8217;usage de la LCG.<br />
<span id="more-34"></span></p>
<h3>Mais c&#8217;est quoi, ce truc ?</h3>
<p>La LCG, ou Lightweight Code Generation, fait référence à une nouveauté apparue dans la seconde version du Framework .NET. </p>
<p>Il a toujours été possible en .NET d&#8217;utiliser l&#8217;API du namespace <code><a href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.aspx">System.Reflection.Emit</a></code> pour générer des assemblies, des modules et des types dynamiquement. Le principe est simple : on autorise via cette API les développeurs de la plateforme .NET à produire directement du code intermédiaire. C&#8217;est extrêmement puissant, mais très rapidement complexe. Le fait que le CIL ainsi obtenu soit (<a href="http://blogs.msdn.com/jmstall/archive/2005/02/03/366429.aspx">quasiment</a>) impossible à débuguer ne vient pas arranger les choses.</p>
<p>C&#8217;était en quelque sorte la Heavyweight Code Generation : pour générer dynamiquement le <a href="http://en.wikipedia.org/wiki/Bytecode">bytecode</a> correspondant à un traitement, il fallait la plupart du temps se taper la création d&#8217;un assembly, d&#8217;un module et d&#8217;un type pour finalement héberger la méthode encapsulant l&#8217;opération. Tout cela est souvent nécessaire, mais le reste du temps, c&#8217;est juste lourd.</p>
<p>Typiquement, lorsqu&#8217;on utilise la génération de CIL pour créer un proxy dynamiquement, on veut définir un type complet avec ses membres et ses méthodes. En revanche, pour créer une simple méthode au runtime on préfèrerait s&#8217;en passer.</p>
<p>La LCG autorise justement la création de méthodes dynamiques pouvant être réclamées par le garbage collector, et surtout ayant la capacité d&#8217;être hébergées anonymement, sans que l&#8217;on ait à créer d&#8217;assembly, de module ou de type. Outre le fait que de telles méthodes soient relativement faciles à créer (je ne parle pas de la génération de leurs corps), elles peuvent également être invoquées via des délégués. Et ça on connait, c&#8217;est efficace.</p>
<h3>Parfois, c&#8217;est inutile</h3>
<p>Dans l&#8217;article précédent, nous avons vu comment il était possible de créer un délégué pointant sur un <code>MethodInfo</code> pour considérablement optimiser les invocations dynamiques. L&#8217;exemple s&#8217;y prêtait bien : nous connaissions la signature de la méthode à appeler dynamiquement donc nous pouvions :</p>
<ul>
<li>Définir un type de délégué correspondant</li>
<li>Solliciter la méthode <code>CreateDelegate</code> de la classe <code>Delegate</code></li>
<li>Invoquer le délégué ainsi récupéré</li>
</ul>
<p>C&#8217;est vraiment ce qu&#8217;il faut retenir. <strong>Il n&#8217;y a aucune raison d&#8217;utiliser la LCG si les signatures des méthodes à appeler dynamiquement sont connues</strong> et qu&#8217;il est possible de définir les délégués correspondants.</p>
<h3>Un exemple moins ingrat</h3>
<p>Vous aurez compris que lorsque la signature des méthodes à appeler dynamiquement n&#8217;est pas connue durant le design, il est impossible de déclarer un délégué correspondant à la méthode que l&#8217;on veut appeler. Il en découle que l&#8217;invocation via délégué est à oublier, et qu&#8217;il ne reste donc que la bonne vieille méthode <code>Invoke</code> sur le <code>MethodInfo</code>.</p>
<p>Oui, celle-la même qui ruine les performances.</p>
<p>A l&#8217;origine, je voulais trouver un exemple ni trop idiot ni trop complexe pour mettre ce cas en évidence et introduire la LCG. Finalement, je n&#8217;ai pas été capable de trouver quelque chose respectant cet équilibre. Vous aurez donc droit à un exemple vraiment simple et super idiot : le <strong>cloneur</strong>.</p>
<p>Commençons par en définir l&#8217;interface :</p>
<p><pre class="brush: csharp;">
public interface ICloner
{
    object Clone(object toClone);
}
</pre></p>
<p>Les types respectant ce contrat devront fournir via la méthode <code>Clone</code> un service capable de retourner un <a href="http://www.devx.com/tips/Tip/13625">shallow clone</a> de l&#8217;objet passé en paramètre. Pour simplifier ici, nous ne considèrerons que les propriétés publiques des objets.</p>
<p>Ecrivons directement un test unitaire :</p>
<p><pre class="brush: csharp;">
[Test]
public void Test()
{
    var cloner = GetCloner();
    var p = new Person
            {
               Id = 1,
               Firstname = &quot;Romain&quot;,
               Lastname = &quot;Verdier&quot;,
               BirthDate = new DateTime(1976, 03, 02),
               Height = 1.65
            };
    var p2 = cloner.Clone(p) as Person;
    Assert.AreNotEqual(p,p2);
    Assert.AreEqual(p.Id, p2.Id);
    Assert.AreEqual(p.Firstname, p2.Firstname);
    Assert.AreEqual(p.Lastname, p2.Lastname);
    Assert.AreEqual(p.BirthDate, p2.BirthDate);
    Assert.AreEqual(p.Height, p2.Height);
}
</pre></p>
<p>En utilisant simplement la réflexion, on peut proposer l&#8217;implémentation non optimisée suivante :</p>
<p><pre class="brush: csharp;">
public class Cloner : ICloner
{
    private readonly Func&lt;Type, Func&lt;object, object&gt;&gt; clonerLocator;

    public Cloner()
    {
        this.clonerLocator = ((Func&lt;Type, Func&lt;object, object&gt;&gt;)GetCloner).Memoize();
    }

    public object Clone(object toClone)
    {
        var cloner = this.clonerLocator(toClone.GetType());
        return cloner(toClone);
    }

    private Func&lt;object, object&gt; GetCloner(Type type)
    {
        var constructorInfo = type.GetConstructor(Type.EmptyTypes);
        if (constructorInfo == null)
        {
            throw new ArgumentException(string.Format(&quot;'{0}' type doesn't have a default constructor.&quot;, type.Name));
        }

        return toClone =&gt;
        {
            var clone = Activator.CreateInstance(type);
            var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (var propertyInfo in propertyInfos)
            {
                var setterInfo = propertyInfo.GetSetMethod();
                var getterInfo = propertyInfo.GetGetMethod();
                if (setterInfo != null &amp;&amp; getterInfo != null)
                {
                    // Deux invocations dynamiques ont lieu ici sans que l'on puisse
                    // utiliser de délégués.
                    setterInfo.Invoke(clone, new object[] {getterInfo.Invoke(toClone, null)});
                }
            }
            return clone;
        }
    }
}
</pre></p>
<p>Notons à propos du code précédent :</p>
<ul>
<li>Peu de vérifications sont faites, c&#8217;est à la fois volontaire et mal.</li>
<li>Nous n&#8217;utilisons pas <code>GetValue</code> et <code>SetValue</code> sur les <code>PropertyInfo</code> pour rendre les invocations dynamiques de méthodes explicites : ici, on fait deux invocations dynamiques par propriété. Une sur le getter, et une sur le setter.</li>
<li>Ne pas connaitre le type de chaque propriété à l&#8217;avance signifie que l&#8217;on ne connait pas la signature des getters et des setters. Ne pas connaître la signature des getters et des setters nous empêche d&#8217;utiliser la méthode décrite dans l&#8217;article précédent.</li>
<li>La <a href="http://codingly.com/2008/05/02/optimisation-des-invocations-dynamiques-de-methodes-en-c/">memoization</a> et l&#8217;utilisation d&#8217;un locateur ne servent qu&#8217;à faciliter la comparaison que l&#8217;on pourra faire avec la prochaine solution, puisqu&#8217;on ne peut pas utiliser de caching dans celle-ci.</li>
</ul>
<p>Puisque vous aimez les chiffres comme tout le monde, j&#8217;ai créé un test de performances effectuant un million d&#8217;appels à la méthode de clonage. Et houlala, c&#8217;est lent : <strong>1000000 appels en plus de 26 secondes</strong>.</p>
<p>En utilisant la LCG, il va être possible de générer une méthode encapsulant la logique de clonage relative à un type donné. On pourra également créer un délégué pour cette méthode, afin qu&#8217;elle puisse être invoquée sans que les performances ne soient dégradées. Ce délégué &#8211; et on rejoint ici le principe d&#8217;optimisation commun à toutes les solutions &#8211; pourra être mis en cache pour éviter que la méthode dynamique ne soit regénérée systématiquement.</p>
<p>Voyons ce que ça donne :</p>
<p><pre class="brush: csharp;">
public class CilCloner : ICloner
{
    private readonly Func&lt;Type, Func&lt;object, object&gt;&gt; clonerLocator;

    public Cloner()
    {
        this.clonerLocator = ((Func&lt;Type, Func&lt;object, object&gt;&gt;) GetCloner).Memoize();
    }

    public object Clone(object toClone)
    {
        var cloner = this.clonerLocator(toClone.GetType());
        return cloner(toClone);
    }

    private Func&lt;object, object&gt; GetCloner(Type type)
    {
        var constructorInfo = type.GetConstructor(Type.EmptyTypes);
        if (constructorInfo == null)
        {
            throw new ArgumentException(string.Format(&quot;'{0}' type doesn't have a default constructor.&quot;, type.Name));
        }

        var dynamicMethod = new DynamicMethod(string.Format(&quot;&lt;{0}&gt;DoClone&quot;, type.Name),
                                              typeof (object),
                                              new []{typeof (object)},
                                              this.GetType());

        var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);

        var gen = dynamicMethod.GetILGenerator();
        var local = gen.DeclareLocal(type);
        gen.Emit(OpCodes.Newobj, constructorInfo);
        gen.Emit(OpCodes.Stloc, local);
        foreach(var propertyInfo in propertyInfos)
        {
            var setterInfo = propertyInfo.GetSetMethod();
            var getterInfo = propertyInfo.GetGetMethod();
            if (setterInfo != null &amp;&amp; getterInfo != null)
            {
                gen.Emit(OpCodes.Ldloc, local);
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Callvirt, getterInfo);
                gen.Emit(OpCodes.Callvirt, setterInfo);
            }
        }
        gen.Emit(OpCodes.Ldloc, local);
        gen.Emit(OpCodes.Ret);

        return (Func&lt;object, object&gt;) dynamicMethod.CreateDelegate(typeof (Func&lt;object, object&gt;));
    }
}
</pre></p>
<p>Décortiquons une fois de plus la solution, en gardant à l&#8217;esprit que le but de l&#8217;article n&#8217;est pas d&#8217;apprendre à coder en CIL :</p>
<ul>
<li>Le membre <code>clonerLocator</code> est gardé en champ d&#8217;instance afin que l&#8217;on puisse le mémoizer.</li>
<li>Le constructeur s&#8217;occupe de cette tâche en faisant appel à la méthode d&#8217;extension <code>Memoize</code> dont vous pourrez (re)trouver la définition dans l&#8217;<a href="http://codingly.com/2008/05/02/optimisation-des-invocations-dynamiques-de-methodes-en-c/">article précédent</a>.</li>
<li>La méthode <code>Clone</code>, correspondant à l&#8217;implémentation de l&#8217;interface <code>ICloner</code>, se contente de récupérer via le locateur un délégué capable d&#8217;effectuer le clone de l&#8217;objet passé en paramètre. Elle l&#8217;invoque directement ensuite.</li>
<li>Le cœur de la solution réside donc dans la méthode <code>GetCloner</code> :
<ul>
<li>Une <a href="http://www.refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html">guard clause</a> permet de vérifier si le type de l&#8217;objet à créer expose bien un constructeur par défaut.</li>
<li>Une <code>DynamicMethod</code> est crée. Il s&#8217;agit d&#8217;une méthode prenant en paramètre un <code>object</code> et retournant un <code>object</code>, qui sera capable de cloner un objet du type <code>type</code></li>
<li>Grâce à la réflexion, on récupère les métadonnées de toutes les propriétés publiques du type <code>type</code>.</li>
<li>On récupère le générateur de code IL de la méthode dynamique afin de pouvoir émettre le corps de cette dernière.</li>
<li>On génère le code IL correspondant à la déclaration d&#8217;une variable locale de type <code>type</code>. On l&#8217;initialise avec une nouvelle instance de <code>type</code>.</li>
<li>On itère sur toutes les métadonnées des propriétés, et on ne considère que celles qui sont à la fois accessibles en lecture et en écriture.</li>
<li>A chaque fois que cette condition est vérifiée, on produit le code correspondant à l&#8217;affectation de la propriété du clone (appel au setter). La valeur utilisée pour l&#8217;affectation est celle récupérée par le getter sur la propriété de l&#8217;objet à cloner.</li>
<li>On termine la construction du corps de la <code>DynamicMethod</code> en émettant les instructions IL correspondant au retour du clone.</li>
<li>Enfin, la méthode <code>GetCloner</code> crée un délégué à partir de la méthode dynamique via <code>CreateDelegate</code>, et le renvoit.</li>
</ul>
</li>
</ul>
<p>C&#8217;est beau ! La mesure des performances en utilisant le même test que précédemment indique cette fois <strong>qu&#8217;un million de clones ont été créés en 305 ms</strong>. Pour information, la méthode <code>MemberwiseClone</code> donne un résultat de 234 ms.</p>
<h3>Conclusion</h3>
<p>La Lightweight Code Generation, et plus globalement l&#8217;utilisation de <code>Reflection.Emit</code>, permet d&#8217;apporter des solutions insoupçonnées à certains problèmes bien spécifiques. Cependant, elle n&#8217;est pas gratuite et demande un investissement non négligeable de la part de ceux qui veulent la maitriser ou bien même s&#8217;en servir ponctuellement. Les développements peuvent être nettement ralentis tandis que les phases de debug et de maintenance risquent de devenir critiques.</p>
<p>Il est donc surtout important de :</p>
<ul>
<li>Savoir que la technique existe.</li>
<li>Savoir reconnaitre les scénarios qui rendent son emploi envisageable.</li>
</ul>
<p>Il existe assez peu de bonnes ressources permettant d&#8217;apprendre à maitriser cette technique. Je suis en train de lire <a href="http://www.amazon.com/CIL-Programming-Under-Hood-NET/dp/1590590414">CIL Programming: Under the Hood of .NET</a> de <a href="http://www.jasonbock.net/JB">Jason Bock</a>, sans être spécialement séduit. Je conseille aux plus curieux de commencer par la MSDN et Google, puis de consulter les sources de projets qui utilisent l&#8217;émission de bytecode. Le blog de <a href="http://evain.net/blog/">Jean-Baptiste Evain</a> est également riche en infos au sujet du CIL : il est l&#8217;auteur entre autres de <a href="http://www.mono-project.com/Cecil">Mono.Cecil</a>.</p>
<p>Mais j&#8217;y reviendrai.</p>
<br /><img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/romainverdier.wordpress.com/34/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/romainverdier.wordpress.com/34/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/romainverdier.wordpress.com/34/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/romainverdier.wordpress.com/34/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/romainverdier.wordpress.com/34/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/romainverdier.wordpress.com/34/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/romainverdier.wordpress.com/34/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/romainverdier.wordpress.com/34/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/romainverdier.wordpress.com/34/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/romainverdier.wordpress.com/34/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&amp;blog=3510695&amp;post=34&amp;subd=romainverdier&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://codingly.com/2008/05/06/continuer-loptimisation-avec-la-lightweight-code-generation-lcg/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/cb0ee4bde49708f4be24a02a5d59e52e?s=96&#38;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">Romain</media:title>
		</media:content>
	</item>
		<item>
		<title>Optimisation des invocations dynamiques de méthodes en C#</title>
		<link>http://codingly.com/2008/05/02/optimisation-des-invocations-dynamiques-de-methodes-en-c/</link>
		<comments>http://codingly.com/2008/05/02/optimisation-des-invocations-dynamiques-de-methodes-en-c/#comments</comments>
		<pubDate>Fri, 02 May 2008 20:52:51 +0000</pubDate>
		<dc:creator>Romain Verdier</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[CIL]]></category>
		<category><![CDATA[Optimisation]]></category>
		<category><![CDATA[Reflection]]></category>

		<guid isPermaLink="false">http://romainverdier.wordpress.com/?p=31</guid>
		<description><![CDATA[Je travaille actuellement en tant que consultant .NET sur un projet d&#8217;une certaine taille. Travailler sur un projet d&#8217;une certaine taille ne signifie pas forcément que l&#8217;on travaille sur un projet intéressant, mais ça augmente sensiblement les chances de rencontrer de nouveaux problèmes. Il n&#8217;est pas question aujourd&#8217;hui de définir ce qu&#8217;est un projet d&#8217;une [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&amp;blog=3510695&amp;post=31&amp;subd=romainverdier&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Je travaille actuellement en tant que consultant .NET sur un projet d&#8217;une certaine taille.</p>
<p>Travailler sur un projet d&#8217;une certaine taille ne signifie pas forcément que l&#8217;on travaille sur un projet intéressant, mais ça augmente sensiblement les chances de rencontrer de nouveaux problèmes. Il n&#8217;est pas question aujourd&#8217;hui de définir ce qu&#8217;est un projet d&#8217;une certaine taille, ni même de démontrer le postulat précédent ; il s&#8217;agit plutôt de parler d&#8217;une des dernières problématiques auxquelles j&#8217;ai dû faire face :</p>
<p>Comment éviter que les <strong>invocations dynamiques de méthodes via la réflexion</strong> rendent les performances d&#8217;une application ou d&#8217;un module catastrophiques ?</p>
<p>Réponse : En minimisant l&#8217;utilisation de la réflexion. C&#8217;est ce que je vais tenter de développer à travers un exemple directement inspiré du projet réel, quoiqu&#8217;adapté pour les besoins de l&#8217;article. Le langage utilisé sera C# 3.0, mais rien n&#8217;empêche d&#8217;utiliser C# 2.0.<br />
<span id="more-31"></span></p>
<h3>Invocation dynamique de méthodes</h3>
<p>Le langage C# supporte la <a href="http://msdn.microsoft.com/en-us/library/f7ykdhsy.aspx">réflexion</a>. Dès lors il est envisageable d&#8217;appeler dynamiquement des méthodes. La puissance de ce mécanisme est souvent contrebalancée par son coût. Prenons un exemple basique :</p>
<p><pre class="brush: csharp;">
public void CallMethod(object target, string methodName)
{
    Type type = target.GetType();
    MethodInfo methodInfo = type.GetMethod(methodName);
    if (methodInfo != null)
    {
        methodInfo.Invoke(target, null);
    }
}
</pre></p>
<p>La fonction précédente est capable d&#8217;exécuter une méthode (sans valeur de retour ni paramètre) à partir de son nom, sur un objet donné. Cette implémentation est un désastre mais là n&#8217;est pas le vrai problème. Le vrai problème est inhérent aux <strong>performances</strong>. Essayons de nous concentrer sur ce qui coûte :</p>
<ul>
<li>L&#8217;appel à <code>GetType</code> pour récupérer le type de l&#8217;objet ne coûte quasiment rien. Nous pouvons considérer que la durée de son exécution est négligeable.</li>
<li>L&#8217;appel à <code>GetMethod</code> pour récupérer les métadonnées relatives à la méthode peut également être ignoré. Attention toutefois : le coût peut ici varier en fonction de la surcharge choisie pour <code>GetMethod</code>, et du type sur lequel on l&#8217;appelle.</li>
<li>L&#8217;appel à <code>Invoke</code> pour exécuter dynamiquement la méthode prend <strong>beaucoup de temps</strong>. En fait, c&#8217;est ce qui est le plus coûteux. Dans un simple test consistant à invoquer dynamiquement la méthode <code>Clone</code> sur une chaine de caractères, la durée d&#8217;exécution s&#8217;est avérée être plus de <strong>400 fois</strong> supérieure à celle nécessaire à un appel classique.</li>
</ul>
<p>Mais relativisons. Si vous êtes dans un scénario impliquant une seule invocation dynamique, vous n&#8217;allez sans doute jamais avoir à vous préoccuper des performances. Qu&#8217;un appel de méthode vous coûte 2 microsecondes au lieu de 4 nanosecondes n&#8217;est pas forcément dramatique ; en vérité, ça l&#8217;est rarement dans la plupart des applications.</p>
<p>Les scénarios critiques sont ceux qui vous forcent à utiliser la réflexion et l&#8217;invocation dynamique pour des tâches <strong>cruciales</strong> et <strong>récurrentes</strong>. Les petites microsecondes font les grandes décennies, etc.</p>
<p>Essayons donc de se mettre en situation en prenant un exemple un peu plus sérieux.</p>
<h3>Il nous faut la réflexion</h3>
<p>Admettons que le rôle d&#8217;un module clé de notre application soit de traiter un flux de données ; ces données étant des <a href="http://en.wikipedia.org/wiki/Data_Transfer_Object">DTO</a> quelconques. Une des tâches du module consiste à inspecter les propriétés de ces objets, ou plus précisément, à déterminer si la valeur courante de chaque propriété correspond à la valeur par défaut du type de la propriété.</p>
<p>Un test unitaire vaut 1000 mots. Voici celui qui valide (presque) le fonctionnement d&#8217;un composant capable d&#8217;effectuer la vérification dont nous venons de parler :</p>
<p><pre class="brush: csharp;">
[Test]
public void ShouldBeAbleToFindDefaultValues()
{
    IDefaultValueTester tester = CreateDefaultValueTester();

    Assert.IsFalse(tester.IsDefaultValue(typeof(int), 12));
    Assert.IsTrue(tester.IsDefaultValue(typeof(int), 0));
    Assert.IsTrue(tester.IsDefaultValue(typeof(double?), null));
    Assert.IsTrue(tester.IsDefaultValue(typeof(string), null));
    Assert.IsFalse(tester.IsDefaultValue(typeof(string), &quot;test&quot;));
    Assert.IsFalse(tester.IsDefaultValue(typeof(DateTime), DateTime.Now));
    Assert.IsTrue(tester.IsDefaultValue(typeof(DateTime), new DateTime()));
}
</pre></p>
<p>L&#8217;interface <code>IDefaultValueTester</code> est simple :</p>
<p><pre class="brush: csharp;">
public interface IDefaultValueTester
{
    bool IsDefaultValue(Type type, object value);
}
</pre></p>
<p>L&#8217;objet de cet article n&#8217;est pas vraiment de trouver un moyen de faire passer ce test. Il existe d&#8217;ailleurs  une solution évidente, bien que peu élégante, qui consisterait à utiliser un switch-like. Mais il ne faut pas oublier qu&#8217;en .NET, un type peut être :</p>
<ul>
<li>Un type référence : <code>class</code>, <code>interface</code>.</li>
<li>Un type valeur : type de base (<code>int</code>, <code>double</code>, <code>bool</code>, etc.), <code>enum</code>, <code>struct</code>.</li>
<li>Un type <a href="http://blogs.msdn.com/ericgu/archive/2004/05/27/143221.aspx">nullable</a> : implémentation de la classe générique <code>Nullable&lt;T&gt;</code> (<code>int?</code>, <code>double?</code>, <code>bool?</code>, etc.)</li>
</ul>
<p>Dès lors, il est assez facile d&#8217;imaginer que le switch sera immonde, et pas forcément performant puisqu&#8217;il faudrait dans certains cas &#8211; je pense aux structures &#8211;  instancier des objets &#8220;templates&#8221; servant de base aux comparaisons.</p>
<p>J&#8217;ai pensé à une solution différente, basée sur les generics. Il est possible qu&#8217;il y ait encore plus malin, mais celle-ci a pour avantage de constituer un bon support pour cet article :</p>
<ul>
<li>Elle nécessite que l&#8217;on recoure à l&#8217;invocation dynamique de méthode.</li>
<li>Elle est lente, donc bonne candidate à l&#8217;optimisation.</li>
</ul>
<p>Le principe consiste à tirer partie du mot clé <code><a href="http://msdn.microsoft.com/en-us/library/xwth0h0d(VS.80).aspx">default</a></code> (capable de retourner la valeur par défaut d&#8217;un type) que l&#8217;on utilise souvent dans le contexte de la généricité. Voyons donc comment l&#8217;exploiter pour implémenter le cœur de cette solution :<br />
<pre class="brush: csharp;">
public static bool IsDefaultValue&lt;T&gt;(object value)
{
    var type = typeof(T);

    if (!type.IsValueType)
        return value == null;

    if (value != null)
        return value.Equals(default(T));

    if (type.IsGenericType &amp;&amp; type.GetGenericTypeDefinition() == typeof(Nullable&lt;&gt;))
        return true;

    var message = &quot;The value type '{0}' can't be null.&quot;;
    throw new ArgumentException(string.Format(message, type.Name));
}
</pre></p>
<p>Tout le monde aura noté la subtile différence qu&#8217;il y a entre le prototype de cette méthode et celui de l&#8217;unique méthode <code>IsDefaultValue</code> de l&#8217;interface <code>IDefaultValueTester</code> : le type qui était passé sous la forme d&#8217;un objet de type <code>Type</code> se retrouve à présent en paramètre de type <code>T</code> d&#8217;une méthode générique. Et c&#8217;est là qu&#8217;interviennent la réflexion et l&#8217;invocation dynamique de méthode.</p>
<p>Examinons donc l&#8217;implémentation de <code>IDefaultValueTester</code> que je propose :</p>
<p><pre class="brush: csharp;">
public class DefaultValueTester : IDefaultValueTester
{
    public bool IsDefaultValue(Type type, object value)
    {
        // On récupère les métadonnées de la méthode générique statique
        // IsDefaultValue présente dans la classe courante.
        var thisType = typeof(DefaultValueTester);
        var methodInfo = thisType.GetMethod(&quot;IsDefaultValue&quot;, 
                                            BindingFlags.Static 
                                            | BindingFlags.Public);

        // On lui spécifie au runtime son paramètre de type générique
        methodInfo = methodInfo.MakeGenericMethod(type);

        // On invoque dynamiquement la méthode générique fermée
        return (bool) methodInfo.Invoke(null, new [] {value});
    }
    
    public static bool IsDefaultValue&lt;T&gt;(object value)
    {
        // cf. extrait de code précédent.
    }
}
</pre></p>
<p>La réflexion est donc nécessaire pour spécifier lors de l&#8217;exécution le paramètre de type de la méthode générique. Une fois la méthode fermée, on utilise <code>Invoke</code> pour l&#8217;exécuter dynamiquement. Résultat : le test passe. </p>
<p><img src="http://romainverdier.files.wordpress.com/2008/05/defaultvaluetestertest.png?w=720" alt="Résultats des tests unitaires" /></p>
<h3>Peut-on optimiser ?</h3>
<p>Oui, et heureusement. Car si on s&#8217;en tient à la description du contexte, il serait assez irresponsable d&#8217;utiliser ce <code>DefaultValueTester</code> au niveau du composant clé de notre architecture. Rappelons que ce dernier est censé inspecter les propriétés de tous les objets arrivant sur le flux. Si nous considérons qu&#8217;il s&#8217;agit d&#8217;un flux faisant parvenir au module des milliers d&#8217;objets par seconde, l&#8217;optimisation n&#8217;est plus une option.</p>
<p>Voici un nouveau test qui nous permet d&#8217;avoir une idée à propos des performances de la solution actuelle :</p>
<p><pre class="brush: csharp;">
[Test]
public void Test()
{
    var stopwatch = new Stopwatch();
    const int iterationCount = 100000;
    stopwatch.Start();

    IDefaultValueTester tester = CreateDefaultValueTester();
    for (var i = 0; i &lt; iterationCount; i++)
    {
        tester.IsDefaultValue(typeof(int), 12);
        tester.IsDefaultValue(typeof(int), 0);
        tester.IsDefaultValue(typeof(double?), null);
        tester.IsDefaultValue(typeof(string), null);
        tester.IsDefaultValue(typeof(string), &quot;test&quot;);
        tester.IsDefaultValue(typeof(DateTime), DateTime.Now);
        tester.IsDefaultValue(typeof(DateTime), new DateTime());
    }

    stopwatch.Stop();
    Console.WriteLine(string.Format(&quot;{0} iteration(s) in {1} ms.&quot;, 
                                    iterationCount, 
                                    stopwatch.ElapsedMilliseconds));
}
</pre></p>
<p>Les 100000 itérations ont été effectuées en <strong>10625 ms</strong>. C&#8217;est loin d&#8217;être terrible.</p>
<p>Il existe au moins deux façons différentes d&#8217;optimiser les invocations dynamiques de méthodes en C# dans un tel scénario. Les deux introduisent la notion de <strong>caching</strong> et se basent sur les <strong>délégués</strong>.</p>
<ol>
<li>Première solution :
<ul>
<li>Utiliser la <a href="http://msdn.microsoft.com/en-us/magazine/cc163759.aspx">Lightweight Code Generation (LCG)</a> via <code>Reflection.Emit</code> pour générer dynamiquement le code CIL correspondant à l&#8217;appel de la méthode générique <code>IsDefaultValue&lt;T&gt;</code> fermée sur le bon type.</li>
<li>Utiliser la méthode <code>CreateDelegate</code> de <code>DynamicMethod</code> pour récupérer un délégué pointant sur la méthode que l&#8217;on vient de générer.</li>
</ul>
</li>
<li>Seconde solution :
<ul>
<li>Conserver l&#8217;usage de la réflexion pour fermer le type de la méthode générique (appel à la méthode <code>MakeGenericMethod</code>) et récupérer le <code>MethodInfo</code> correspondant.</li>
<li>Utiliser la méthode statique <code>CreateDelegate</code> de la classe abstraite <code>Delegate</code> pour récupérer un délégué à partir du <code>MethodInfo</code> précédent.</li>
</ul>
</li>
</ol>
<p>Les délégués ainsi obtenus ont deux particularités qui les rendent précieux dans le contexte de cette optimisation :</p>
<ul>
<li>Le coût de leur invocation est quasiment nul, contrairement à l&#8217;appel à la méthode <code>Invoke</code> sur un <code>MethodInfo</code>.</li>
<li>Ils peuvent être mis en cache, et indexés intelligemment de façon à ce qu&#8217;ils ne soient pas recréés à chaque fois. Ici, il suffit d&#8217;utiliser le type des propriétés comme clé.</li>
</ul>
<p>Retenons ici la seconde solution, qui est plus simple à développer, maintenir et tester, et voyons ce que cela donne :<br />
<pre class="brush: csharp;">
using TesterMethodDelegate = Func&lt;object, bool&gt;;

public class OptimizedDefaultValueTester : IDefaultValueTester
{
    private readonly Dictionary&lt;Type, TesterMethodDelegate&gt; cache = new Dictionary&lt;Type, TesterMethodDelegate&gt;();

    public bool IsDefaultValue(Type type, object value)
    {
        var tester = GetTesterMethodDelegate(type, value);
        return tester(value);
    }

    private TesterMethodDelegate GetTesterMethodDelegate(Type type, object value)
    {
        TesterMethodDelegate tester;
        if(!this.cache.TryGetValue(type, out tester))
        {
            var thisType = typeof(OptimizedDefaultValueTester);
            var methodInfo = thisType.GetMethod(&quot;IsDefaultValue&quot;, BindingFlags.Static | BindingFlags.Public);
            methodInfo = methodInfo.MakeGenericMethod(type);
            tester = (TesterMethodDelegate) Delegate.CreateDelegate(typeof(TesterMethodDelegate), methodInfo);
            this.cache.Add(type, tester);
        }
        return tester;
    }

    public static bool IsDefaultValue&lt;T&gt;(object obj)
    {
            // cf. extraits de code précédents. 
    }
}
</pre></p>
<p>Les points remarquables :</p>
<ul>
<li>Ligne 1 : On utilise le type de délégué générique <code><a href="http://msdn.microsoft.com/en-us/library/bb549151.aspx">Func&lt;T,TReturn&gt;</a></code> du Framework 3.5, derrière un alias (<code>TesterMethodDelegate</code>).</li>
<li>Ligne 5 : Les instances de <code>TesterMethodDelegate</code> sont mises en cache grâce à un champ d&#8217;instance de type <code>Dictionary&lt;TKey,TValue&gt;</code> et sont indexées par type.</li>
<li>Ligne 16 : A chaque appel à <code>IsDefaultValue</code>, on regarde si un <code>TesterMethodDelegate</code> a déjà été créé pour le type passé en paramètre. Si c&#8217;est le cas, on récupère l&#8217;instance dans le cache, sinon, on la crée avant de l&#8217;ajouter au cache.</li>
<li>Ligne 10 : L&#8217;appel dynamique via la méthode <code>Invoke</code> a disparu, on invoque directement l&#8217;instance de <code>TesterMethodDelegate</code> récupérée.</li>
</ul>
<p>Toujours selon le même test, les performances sont améliorées. On passe de <strong>10625 ms</strong> pour 700000 appels, à <strong>506 ms</strong>. C&#8217;est environ 20 fois mieux, hourra.</p>
<h3>Peut-on aller plus loin ?</h3>
<p>Pas vraiment, si on ne considère que les performances. Par contre, il est possible d&#8217;encapsuler le mécanisme d&#8217;optimisation précédent pour favoriser la réutilisabilité. Et pour cela, nous pouvons utiliser la <a href="http://en.wikipedia.org/wiki/Memoization"><strong>memoization</strong></a>. Sans le savoir c&#8217;est un peu ce que nous avons imaginé jusqu&#8217;ici.</p>
<p>Cependant, il est possible en utilisant les <a href="http://msdn.microsoft.com/en-us/library/0yw3tz5k(vs.80).aspx">méthodes anonymes</a> (ou les <a href="http://msdn.microsoft.com/en-us/library/bb397687.aspx">expressions lambda</a> en C# 3.0) de mettre en place une solution plus élégante, et autorisant la réutilisation du caching comme s&#8217;il s&#8217;agissait en quelque sorte d&#8217;un <a href="http://en.wikipedia.org/wiki/Aspect_%28computer_science%29">aspect</a>. J&#8217;ai découvert cela en tombant sur ce <a href="http://blogs.msdn.com/wesdyer/archive/2007/01/26/function-memoization.aspx">post</a> et j&#8217;ai été séduit.</p>
<p>Le principe consiste à créer une méthode capable de retourner une version mémoizée d&#8217;un délégué. On peut même en faire une <a href="http://en.wikipedia.org/wiki/Extension_method">méthode d&#8217;extension</a> générique en C# 3.0:</p>
<p><pre class="brush: csharp;">
public static class Memoization
{
    public static Func&lt;T, TResult&gt; Memoize&lt;T, TResult&gt;(this Func&lt;T, TResult&gt; function)
    {
        var cache = new Dictionary&lt;T, TResult&gt;();
        var nullCache = default(TResult);
        var isNullCacheSet = false;
        return  parameter =&gt; 
                {
                    TResult value;

                    if (parameter == null &amp;&amp; isNullCacheSet)
                    {
                        return nullCache;
                    }

                    if (parameter == null)
                    {
                        nullCache = function(parameter);
                        isNullCacheSet = true;
                        return nullCache;
                    }

                    if (cache.TryGetValue(parameter, out value))
                    {
                        return value;
                    }

                    value = function(parameter);
                    cache.Add(parameter, value);
                    return value;
                };
    }
}
</pre></p>
<p>Décortiquons cette méthode :</p>
<ul>
<li>Elle prend en paramètre un délégué de type <code>Func&lt;T,TResult&gt;</code> et retourne un délégué du même type. Pour simplifier, on peut dire que la méthode d&#8217;extension prend en paramètre et retourne une fonction dont le prototype est le suivant : <code>TResult Function(T param)</code></li>
<li>La fonction retournée est construite via une expression lambda qui se charge d&#8217;encapsuler la logique de caching. Elle a pour rôle de mémoriser le résultat (de type <code>TResult</code>) de la fonction à chaque valeur différente de <code>T</code> pour laquelle on l&#8217;appelle.</li>
<li>C&#8217;est la valeur sauvegardée qui est retournée lorsqu&#8217;elle est présente dans le cache. En effet, le résultat de la fonction pour un paramètre donné ayant déjà été déterminé, il ne sert à rien d&#8217;exécuter de nouveau la fonction avec le même paramètre.</li>
<li>Dans cet exemple, l&#8217;expression lambda qui sert à créer la fonction retournée est une <a href="http://en.wikipedia.org/wiki/Closure_(computer_science)">closure</a>. C&#8217;est ici que réside toute l&#8217;ingéniosité de cette technique. Le dictionnaire est une variable locale à la méthode <code>Memoize</code> référencée par la méthode lambda, donc du point de vue de cette dernière l&#8217;état du cache sera conservé entre chaque appel.</li>
<li>La méthode doit traiter un cas particulier : Si la valeur <code>null</code> est passée en argument de la fonction, il n&#8217;est plus possible d&#8217;utiliser un dictionnaire pour mettre en cache le résultat puisque les clés de ce dernier ne peuvent être nulles. Nous utilisons donc une variable spécialement dédiée : <code>nullCache</code>.</li>
</ul>
<p>Bon, la memoization, c&#8217;est classe. Mais revenons à notre besoin. Quelle fonction a besoin d&#8217;être mémoizée ? Et bien tout simplement celle qui pour un type donné est capable de nous retourner le délégué pointant sur la bonne version fermée de <code>IsDefaultValue&lt;T&gt;</code>.</p>
<p>Une telle fonction peut avoir le prototype suivant :</p>
<p><pre class="brush: csharp;">
Func&lt;object, bool&gt; GetTesterMethodDelegate(Type type);
</pre></p>
<p>Le type du délégué correspondant est le suivant :</p>
<p><pre class="brush: csharp;">
Func&lt;Type, Func&lt;object, bool&gt;&gt;
</pre></p>
<p>En effet, il s&#8217;agit bien d&#8217;une fonction qui prend un type en argument, et qui retourne une autre fonction prenant un objet en argument et retournant un booléen.</p>
<p>Il nous reste plus qu&#8217;à examiner la nouvelle implémentation de <code>IDefaultValueTester</code> qui se base sur le principe :</p>
<p><pre class="brush: csharp;">
using TesterMethodDelegate = Func&lt;object, bool&gt;;
using TesterMethodLocatorDelegate = Func&lt;Type, Func&lt;object, bool&gt;&gt;;

public class MemoizedDefaultValueTester : IDefaultValueTester
{
    private readonly TesterMethodLocatorDelegate testerLocator;

    public MemoizedDefaultValueTester()
    {
        // On crée une fonction capable de retourner la version fermée 
        // de la méthode IsDefaultValue&lt;T&gt; pour un type donné.
        this.testerLocator = type =&gt;
                             {
                                 var thisType = typeof (OptimizedDefaultValueTester);
                                 var methodInfo = thisType.GetMethod(&quot;IsDefaultValue&quot;, BindingFlags.Static | BindingFlags.Public);
                                 methodInfo = methodInfo.MakeGenericMethod(type);
                                 var tester = (TesterMethodDelegate)Delegate.CreateDelegate(typeof(TesterMethodDelegate), methodInfo);
                                 return tester;
                             };

        // On mémomize cette fonction en appelant notre méthode d'extension
        this.testerLocator = this.testerLocator.Memoize();
    }

    public bool IsDefaultValue(Type type, object value)
    {
        var tester = this.testerLocator(type);
        return tester(value);
    }

    public static bool IsDefaultValue&lt;T&gt;(object obj)
    {
         // cf. extraits de code précédents. 
    }
}
</pre></p>
<p>Nous pouvons constater que :</p>
<ul>
<li>Un nouvel alias (<code>TesterMethodLocatorDelegate</code>) est introduit pour le type générique <code>Func&lt;Type,Func&lt;object,bool&gt;&gt;</code> </li>
<li>La classe possède un champ d&#8217;instance (<code>testerLocator</code>) de type <code>TesterMethodLocatorDelegate</code> qui est initialisé dans le constructeur.</li>
<li>La méthode d&#8217;extension <code>Memoize</code> définie plus tôt est utilisée pour mémoizer le <code>TesterMethodLocatorDelegate</code>, toujours au niveau du constructeur.</li>
<li>L&#8217;implémentation de la méthode <code>IsDefaultValue</code> est extrêmement simplifiée. Via la version mémoizée du <code>TesterMethodLocatorDelegate</code>, on récupère un <code>TesterMethodDelegate</code> qui peut être invoqué directement afin d&#8217;effectuer le test sur la valeur passée en paramètre.</li>
</ul>
<p>Les performances de cette solution sont les mêmes que celles mesurées pour la précédente : environ <strong>510 ms</strong>. Mais à présent, nous disposons d&#8217;une méthode <code>Memoize</code> pouvant être réutilisée.</p>
<p>Hum. Attendez&#8230; Pourquoi ne pas la réutiliser alors, pour memoizer aussi les instances de <code>TesterMethodDelegate</code> retournées par le <code>TesterMethodLocatorDelegate</code> ? Il y aurait ainsi deux niveaux de mémoization, et peut-être à la clé un petit gain de performance supplémentaire.</p>
<p>La modification se limite donc à ajouter un nouvel appel à la méthode <code>Memoize</code> dans le constructeur du <code>MemoizedDefaultValueTester</code> :</p>
<p><pre class="brush: csharp;">
public MemoizedDefaultValueTester()
{
    // On crée une fonction capable de retourner la version fermée 
    // de la méthode IsDefaultValue&lt;T&gt; pour un type donné.
    this.testerLocator = type =&gt;
                         {
                             var thisType = typeof (OptimizedDefaultValueTester);
                             var methodInfo = thisType.GetMethod(&quot;IsDefaultValue&quot;, BindingFlags.Static | BindingFlags.Public);
                             methodInfo = methodInfo.MakeGenericMethod(type);
                             var tester = (TesterMethodDelegate)Delegate.CreateDelegate(typeof(TesterMethodDelegate), methodInfo);

                             // On retourne à présent une version mémoizée du TesterMethodDelegate :
                             return tester.Memoize();
                         };

    // On mémoize cette fonction en appelant notre méthode d'extension
    this.testerLocator = this.testerLocator.Memoize();
}
</pre></p>
<p>Le test des performances indique à présent que les 100000 itérations ont eu lieu en <strong>320 ms</strong> ! Ce n&#8217;est pas aussi efficace que la première étape d&#8217;optimisation, mais proportionnellement cela conduit tout de même à une amélioration significative : environ 30%.</p>
<p><strong>Attention</strong> quand même : Si notre premier usage de la memoization était justifié, le second peut être très dangereux. <a href="http://en.wikipedia.org/wiki/Computational_complexity_theory">Nous gagnons des millisecondes, mais nous perdons des octets</a>. Et dans le scénario actuel, il est même probable que la memoization des <code>TesterMethodDelegate</code> conduise à une <code>OutOfMemoryException</code> rapidement&#8230;</p>
<h3>Conclusion</h3>
<p>Nous nous sommes contentés d&#8217;évoquer la solution d&#8217;optimisation impliquant la génération de code IL. Dans le contexte de la problématique discutée, elle n&#8217;offrait aucun avantage par rapport à celle que nous avons exposée. Pire, elle imposait une étape inutile. Toutefois, certains besoins plus complexes dépassent le cadre des invocations dynamiques de méthodes et peuvent tout de même être adressés efficacement en recourant à la génération de bytecode. Je pense que cela sera le sujet d&#8217;un prochain article.</p>
<p>Quant à la technique détaillée dans celui-ci, elle est simple et permet d&#8217;obtenir des performances acceptables dans la majorité des scénarios pour lesquels l&#8217;utilisation de la réflexion est nécessaire.</p>
<p>Cela fait presque deux raisons de ne plus avoir systématiquement peur de la réflexion. Par contre, si vous n&#8217;en avez pas peur du tout, il serait peut-être temps de s&#8217;y mettre doucement&#8230;</p>
<div class="post-edited-notification">
<strong>Edit </strong>: Un <a href="http://codingly.com/2008/05/06/continuer-loptimisation-avec-la-lightweight-code-generation-lcg/">autre article</a> complète à présent celui-ci.
</div>
<br /><img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/romainverdier.wordpress.com/31/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/romainverdier.wordpress.com/31/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/romainverdier.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/romainverdier.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/romainverdier.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/romainverdier.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/romainverdier.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/romainverdier.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/romainverdier.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/romainverdier.wordpress.com/31/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&amp;blog=3510695&amp;post=31&amp;subd=romainverdier&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://codingly.com/2008/05/02/optimisation-des-invocations-dynamiques-de-methodes-en-c/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/cb0ee4bde49708f4be24a02a5d59e52e?s=96&#38;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">Romain</media:title>
		</media:content>

		<media:content url="http://romainverdier.files.wordpress.com/2008/05/defaultvaluetestertest.png" medium="image">
			<media:title type="html">Résultats des tests unitaires</media:title>
		</media:content>
	</item>
	</channel>
</rss>
