Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><strong>NOTE: There was a bug in my initial code for instance-based code generation. Please re-check the code below. The changed part is the order of loading values onto the stack (ie. the .Emit lines). Both the code in the answer and the repository has been fixed.</strong></p> <p>If you want to go the route of code generation, as you hint to in your question, here's sample code:</p> <p>It executes ConsumeValue (which does nothing in my example) 10 million times, on an array of ints and an array of booleans, timing the execution (it runs all the code once, to remove JIT overhead from skewing the timing.)</p> <p>The output:</p> <pre><code>F1 ints = 445ms &lt;-- uses Convert.ToDouble F1 bools = 351ms F2 ints = 159ms &lt;-- generates code on each call F2 bools = 167ms F3 ints = 158ms &lt;-- caches generated code between calls F3 bools = 163ms </code></pre> <p>Roughly 65% less overhead with code generation.</p> <p>The code is available from my Mercurial repository here: <a href="http://hg.vkarlsen.no/hgweb.cgi/StackOverflow" rel="noreferrer">http://hg.vkarlsen.no/hgweb.cgi/StackOverflow</a>, browse it by finding your SO question number.</p> <p>The code:</p> <pre><code>using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace ConsoleApplication15 { class Program { public static void F1&lt;T&gt;(IList&lt;T&gt; values) where T : struct { foreach (T value in values) ConsumeValue(Convert.ToDouble(value)); } public static Action&lt;T&gt; GenerateAction&lt;T&gt;() { DynamicMethod method = new DynamicMethod( "action", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), new Type[] { typeof(T) }, typeof(Program).Module, false); ILGenerator il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // get value passed to action il.Emit(OpCodes.Conv_R8); il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); il.Emit(OpCodes.Ret); return (Action&lt;T&gt;)method.CreateDelegate(typeof(Action&lt;T&gt;)); } public static void F2&lt;T&gt;(IList&lt;T&gt; values) where T : struct { Action&lt;T&gt; action = GenerateAction&lt;T&gt;(); foreach (T value in values) action(value); } private static Dictionary&lt;Type, object&gt; _Actions = new Dictionary&lt;Type, object&gt;(); public static void F3&lt;T&gt;(IList&lt;T&gt; values) where T : struct { Object actionObject; if (!_Actions.TryGetValue(typeof(T), out actionObject)) { actionObject = GenerateAction&lt;T&gt;(); _Actions[typeof (T)] = actionObject; } Action&lt;T&gt; action = (Action&lt;T&gt;)actionObject; foreach (T value in values) action(value); } public static void ConsumeValue(double value) { } static void Main(string[] args) { Stopwatch sw = new Stopwatch(); int[] ints = Enumerable.Range(1, 10000000).ToArray(); bool[] bools = ints.Select(i =&gt; i % 2 == 0).ToArray(); for (int pass = 1; pass &lt;= 2; pass++) { sw.Reset(); sw.Start(); F1(ints); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F1 ints = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); F1(bools); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F1 bools = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); F2(ints); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F2 ints = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); F2(bools); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F2 bools = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); F3(ints); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F3 ints = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); F3(bools); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F3 bools = " + sw.ElapsedMilliseconds + "ms"); } } } } </code></pre> <p>Note that if you make GenerationAction, F2/3 and ConsumeValue non-static, you have to change the code slightly:</p> <ol> <li>All <code>Action&lt;T&gt;</code> declarations becomes <code>Action&lt;Program, T&gt;</code></li> <li><p>Change the creation of the DynamicMethod to include the "this" parameter:</p> <pre><code>DynamicMethod method = new DynamicMethod( "action", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), new Type[] { typeof(Program), typeof(T) }, typeof(Program).Module, false); </code></pre></li> <li><p>Change the instructions to load the right values at the right times:</p> <pre><code>il.Emit(OpCodes.Ldarg_0); // get "this" il.Emit(OpCodes.Ldarg_1); // get value passed to action il.Emit(OpCodes.Conv_R8); il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); il.Emit(OpCodes.Ret); </code></pre></li> <li><p>Pass "this" to the action whenever it is called:</p> <pre><code>action(this, value); </code></pre></li> </ol> <p>Here's the complete changed program for non-static methods:</p> <pre><code>using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace ConsoleApplication15 { class Program { public void F1&lt;T&gt;(IList&lt;T&gt; values) where T : struct { foreach (T value in values) ConsumeValue(Convert.ToDouble(value)); } public Action&lt;Program, T&gt; GenerateAction&lt;T&gt;() { DynamicMethod method = new DynamicMethod( "action", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), new Type[] { typeof(Program), typeof(T) }, typeof(Program).Module, false); ILGenerator il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // get "this" il.Emit(OpCodes.Ldarg_1); // get value passed to action il.Emit(OpCodes.Conv_R8); il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); il.Emit(OpCodes.Ret); return (Action&lt;Program, T&gt;)method.CreateDelegate( typeof(Action&lt;Program, T&gt;)); } public void F2&lt;T&gt;(IList&lt;T&gt; values) where T : struct { Action&lt;Program, T&gt; action = GenerateAction&lt;T&gt;(); foreach (T value in values) action(this, value); } private static Dictionary&lt;Type, object&gt; _Actions = new Dictionary&lt;Type, object&gt;(); public void F3&lt;T&gt;(IList&lt;T&gt; values) where T : struct { Object actionObject; if (!_Actions.TryGetValue(typeof(T), out actionObject)) { actionObject = GenerateAction&lt;T&gt;(); _Actions[typeof (T)] = actionObject; } Action&lt;Program, T&gt; action = (Action&lt;Program, T&gt;)actionObject; foreach (T value in values) action(this, value); } public void ConsumeValue(double value) { } static void Main(string[] args) { Stopwatch sw = new Stopwatch(); Program p = new Program(); int[] ints = Enumerable.Range(1, 10000000).ToArray(); bool[] bools = ints.Select(i =&gt; i % 2 == 0).ToArray(); for (int pass = 1; pass &lt;= 2; pass++) { sw.Reset(); sw.Start(); p.F1(ints); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F1 ints = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); p.F1(bools); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F1 bools = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); p.F2(ints); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F2 ints = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); p.F2(bools); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F2 bools = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); p.F3(ints); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F3 ints = " + sw.ElapsedMilliseconds + "ms"); sw.Reset(); sw.Start(); p.F3(bools); sw.Stop(); if (pass == 2) Console.Out.WriteLine("F3 bools = " + sw.ElapsedMilliseconds + "ms"); } } } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload