Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p><em><strong>Short answer</em></strong>: It is because <a href="http://msdn.microsoft.com/en-us/library/8bs2ecf4%28v=vs.100%29.aspx" rel="nofollow">CRL</a> has optimization for dispatching methods called on interface-type. As long as particular interface's method call is made on the same type (that implements this interface), CLR uses fast dispatching routine (only 3 instructions) that only checks actual type of instance and in case of match it jumps directly on precomputed address of particular method. But when the same interface's method call is made on instance of another type, CLR switches dispatching to slower routine (which can dispatch methods for any actual instance type).</p> <p><em><strong>Long answer</em></strong>: Firstly, take a look at how the method <a href="http://msdn.microsoft.com/en-us/library/system.linq.enumerable.sum.aspx" rel="nofollow">System.Linq.Enumerable.Sum()</a> is declared (I omitted validity checking of source parameter because it's not important it this case):</p> <pre><code>public static int Sum(this IEnumerable&lt;int&gt; source) { int num = 0; foreach (int num2 in source) num += num2; return num; } </code></pre> <p>So all types that implement <a href="http://msdn.microsoft.com/en-us/library/9eekhta0.aspx" rel="nofollow">IEnumerable&lt; int ></a> can call this extension method, including <em>int[]</em> and <em>List&lt; int ></em>. Keyword <a href="http://msdn.microsoft.com/en-us/library/ttw7t8t6%28v=vs.100%29.aspx" rel="nofollow">foreach</a> is just abbreviation for getting enumerator via <em>IEnumerable&lt; T >.GetEnumerator()</em> and iterating through all values. So this method actually does this:</p> <pre><code> public static int Sum(this IEnumerable&lt;int&gt; source) { int num = 0; IEnumerator&lt;int&gt; Enumerator = source.GetEnumerator(); while(Enumerator.MoveNext()) num += Enumerator.Current; return num; } </code></pre> <p>Now you can clearly see, that method body contains three method calls on interface-type variables: <em>GetEnumerator()</em>, <em>MoveNext()</em>, and <em>Current</em> (although <em>Current</em> is actually property, not method, reading value from property just calls corresponding getter method).</p> <p><em>GetEnumerator()</em> typically creates new instance of some auxiliary class, which implements <em>IEnumerator&lt; T ></em> and thus is able to return all values one by one. It is important to note, that in case of <em>int[]</em> and <em>List&lt; int ></em>, types of enumerators returned by <em>GetEnumerator()</em> ot these two classes are different. If argument <em>source</em> is of type <em>int[]</em>, then <em>GetEnumerator()</em> returns instance of type <em>SZGenericArrayEnumerator&lt; int ></em> and if <em>source</em> is of type <em>List&lt; int ></em>, then it returns instance of type <em>List&lt; int >+Enumerator&lt; int ></em>. </p> <p>Two other methods (<em>MoveNext()</em> and <em>Current</em>) are repeatedly called in tight loop and therefore their speed is crucial for overall performance. Unfortunatelly calling method on interface-type variable (such as <em>IEnumerator&lt; int ></em>) is not as straightforward as ordinary instance method call. CLR must dynamically find out actual type of object in variable and then find out, which object's method implements corresponding interface method.</p> <p>CLR tries to avoid doing this time consuming lookup on <strong>every call</strong> with a little trick. When particular method (such as <em>MoveNext()</em>) is called for the first time, CLR finds actual type of instance on which this call is made (for example <em>SZGenericArrayEnumerator&lt; int ></em> in case you called <em>Sum</em> on <em>int[]</em>) and finds address of method, that implements corresponding method for this type (that is address of method <em>SZGenericArrayEnumerator&lt; int >.MoveNext()</em>). Then it uses this information to generate auxiliary dispatching method, which simply checks, whether actual instance type is the same as when first call was made (that is <em>SZGenericArrayEnumerator&lt; int ></em>) and if it is, it directly jumps to the method's address found earlier. So on subsequent calls, no complicated method lookup is made as long as type of instance remains the same. But when call is made on enumerator of different type (such as <em>List&lt; int >+Enumerator&lt; int ></em> in case of calculating sum of <em>List&lt; int ></em>), CLR no longer uses this fast dispatching method. Instead another (general-purpose) and much slower dispatching method is used.</p> <p><strong>So as long as <em>Sum()</em> is called on array only, CLR dispatches calls to <em>GetEnumerator()</em>, <em>MoveNext()</em>, and <em>Current</em> using fast method. When <em>Sum()</em> is called on list too, CLR switches to slower dispatching method and therefore performance decreases.</strong></p> <p>If performance is your concern, implement your own separate <em>Sum()</em> extension method for every type, on which you want to call <em>Sum()</em>. This ensures that CLR will use fast dispatching method. For example:</p> <pre><code>public static class FasterSumExtensions { public static int Sum(this int[] source) { int num = 0; foreach (int num2 in source) num += num2; return num; } public static int Sum(this List&lt;int&gt; source) { int num = 0; foreach(int num2 in source) num += num2; return num; } } </code></pre> <p>Or even better, avoid using <em>IEnumerable&lt; T ></em> interface at all (because it's still brings noticeable overhead). For example:</p> <pre><code>public static class EvenFasterSumExtensions { public static int Sum(this int[] source) { int num = 0; for(int i = 0; i &lt; source.Length; i++) num += source[i]; return num; } public static int Sum(this List&lt;int&gt; source) { int num = 0; for(int i = 0; i &lt; source.Count; i++) num += source[i]; return num; } } </code></pre> <p>Here are results from my computer:</p> <ul> <li><em>Your original program</em>: 9844, 9841, 12545, <strong>14384</strong></li> <li><em>FasterSumExtensions</em>: 6149, 6445, 754, <strong>6145</strong></li> <li><em>EvenFasterSumExtensions</em>: 1557, 1561, 553, <strong>1574</strong></li> </ul>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

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