× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

<= does work slower than <

James Jensen
Apr 21, 2015
<p>First of all, there are many, many reasons to see variations in benchmarks, even when they're done right. Here are a few that come to mind:</p> <ul> <li>Your computer is running a lot of other processes at the same time, switching things in and out of context, and so on. The operating system is constantly receiving and handling interrupts from various I/O devices, etc. All of these things can cause the computer to pause for periods of time that dwarf the running time for the actual code you're testing. </li> <li>The JIT process can detect when a function has run a certain number of times, and apply additional optimizations to it based on that information. Things like <em>loop unrolling</em> can drastically reduce the number of <em>jumps</em> that the program has to make, which are significantly more expensive than typical CPU operations. Re-optimizing the instructions takes time when it first happens, and then speeds things up after that point. </li> <li>Your hardware is trying to make additional optimizations, like <em>branch prediction</em>, to ensure that its <em>pipeline</em> is being used as efficiently as possible. (If it guesses correctly, it can basically pretend that it's going to do the <code>i++</code> while it waits to see whether the <code>&lt;</code> or <code>&lt;=</code> comparison finishes, and then discard the result if it finds out it was wrong.) The impact of these optimizations depends on a lot of factors, and is not really easy to predict.</li> </ul> <p>Secondly, it's actually really difficult to do benchmarking well. Here'a benchmark template that I've been using for a while now. It's not perfect, but it's pretty good at ensuring that any emerging patterns are unlikely to be based on order of execution or random chance:</p> <pre><code>/* This is a benchmarking template I use in LINQPad when I want to do a * quick performance test. Just give it a couple of actions to test and * it will give you a pretty good idea of how long they take compared * to one another. It's not perfect: You can expect a 3% error margin * under ideal circumstances. But if you're not going to improve * performance by more than 3%, you probably don't care anyway.*/ void Main() { // Enter setup code here var actions = new[] { new TimedAction("control", () =&gt; { int i = 0; }), new TimedAction("&lt;", () =&gt; { for (int i = 0; i &lt; 1000001; i++) {} }), new TimedAction("&lt;=", () =&gt; { for (int i = 0; i &lt;= 1000000; i++) {} }), new TimedAction("&gt;", () =&gt; { for (int i = 1000001; i &gt; 0; i--) {} }), new TimedAction("&gt;=", () =&gt; { for (int i = 1000000; i &gt;= 0; i--) {} }) }; const int TimesToRun = 10000; // Tweak this as necessary TimeActions(TimesToRun, actions); } #region timer helper methods // Define other methods and classes here public void TimeActions(int iterations, params TimedAction[] actions) { Stopwatch s = new Stopwatch(); int length = actions.Length; var results = new ActionResult[actions.Length]; // Perform the actions in their initial order. for(int i = 0; i &lt; length; i++) { var action = actions[i]; var result = results[i] = new ActionResult{Message = action.Message}; // Do a dry run to get things ramped up/cached result.DryRun1 = s.Time(action.Action, 10); result.FullRun1 = s.Time(action.Action, iterations); } // Perform the actions in reverse order. for(int i = length - 1; i &gt;= 0; i--) { var action = actions[i]; var result = results[i]; // Do a dry run to get things ramped up/cached result.DryRun2 = s.Time(action.Action, 10); result.FullRun2 = s.Time(action.Action, iterations); } results.Dump(); } public class ActionResult { public string Message {get;set;} public double DryRun1 {get;set;} public double DryRun2 {get;set;} public double FullRun1 {get;set;} public double FullRun2 {get;set;} } public class TimedAction { public TimedAction(string message, Action action) { Message = message; Action = action; } public string Message {get;private set;} public Action Action {get;private set;} } public static class StopwatchExtensions { public static double Time(this Stopwatch sw, Action action, int iterations) { sw.Restart(); for (int i = 0; i &lt; iterations; i++) { action(); } sw.Stop(); return sw.Elapsed.TotalMilliseconds; } } #endregion </code></pre> <p>Here's the result I get when running this in LINQPad:</p> <p><img src="http://i.stack.imgur.com/4HS4l.png" alt="benchmark result"></p> <p>So you'll notice that there is some variation, particularly early on, but after running everything backwards and forwards enough times, there isn't a clear pattern emerging to show that one way is much faster or slower than another.</p> <p>This tip was originally posted on <a href="http://stackoverflow.com/questions/29702328/&lt;=%20does%20work%20slower%20than%20&lt;/29703746">Stack Overflow</a>.</p>
comments powered by Disqus