Does Parameter Order Make A Difference?
I remember reading once that frequently accessed method variables placed first within a method’s signature are accessed faster as they are allocated for available cpu registers.
As I was refactoring C# code in a financial application that required every little nano-second micro-optimized, I noticed a few functions and methods that had parameters seemingly accessed the most, placed last in their signature.
That’s where this Curious Consultant started wondering… does parameter order make a difference in function and method signatures in C#? What would happen if the parameters in the signature were re-ordered?
Let’s find out!
Constructing the Test
For this test, we needed to create 3 methods:
- a parent method that would call two other methods with the same signature, but different names
- a child method 1 that tests the first 2 parameters being called in a loop
- a child method 2 that tests the last 2 parameters being called in a loop
The trick here though is to ensure an actual difference was registered (ha! no pun intended! 🙂 ) at compile time:
- when testing child method 1, I had all the code commented out that referenced child method 2
- vice versa. When testing child method 2, the code was recompiled with all references to child method 1 commented out
The code is written in Visual Studio 2017 targeting .Net Framework version 4.7.1 x64, compiled in “Release” mode the build option “Optimize Code” checked. The source code is available at the end of this blog so you can benchmark it on your own system.
Assumptions:
- No exception handling as no exceptions should occur
- All the parameters are passed by value, not by reference
The exe file was run under Windows 10 Professional 64-bit with 32GB of memory.
The test was run with each child method called the following number of times:
- 250
- 500
- 5,000
- 25,000
Locked and Loaded!
These tests were run a few times throughout the day.
All times are indicated in minutes.seconds.milliseconds format. Lower numbers indicate faster run time performance.
Winners are highlighted in green; there are no points for second place.
Additional caveat: only the last results are being shown here. All the previous results were similar. That is, the clear winner indicated below was the winner for every single test.
The Results:
In this test scenario, whenever the variables access the most were placed first in the method signature, that performed faster.
|
Number of Times Method Called |
|||
|
250 |
500 |
5,000 |
25,000 |
TestIfParameterOrderMakesADifferenceSub1 |
00:03.2695409 |
00:06.5599658 |
01:03.1584980 |
05:17.8328597 |
TestIfParameterOrderMakesADifferenceSub2 |
00:04.1061960 |
00:08.1929888 |
01:19.8485032 |
06:45.5268578 |
Delta (rounded): |
00:00.8366 |
00:01.6330 |
00:16.6901 |
01:27.6940 |
In Summary:
Does parameter order make a difference? In C#, it’s quite evident from the results that placing the variables utilized the most first in a method’s signature does increase performance.
As the number of times the child method was called increased, the difference between the two became more and more readily apparent. The difference in execution times, despite doing almost the exact same thing, was nearly 90 seconds for 25,000 calls. In the computing world where processors are running at super-high frequencies, that’s HUGE. If the methods are in a real-time financial application, intense computer game, or where instant results matters most, any programmer should definitely reconsider refactoring their method calls and signatures.
In the more practical world, taking into account at the top end of the test the methods were called 25,000 times, which does nothing but loop 10,000,000 times, the speed difference will probably be negligible to the end user. In fact, they’ll probably blame it on their computer’s internet connection or their operating system’s lack of responsiveness.
As for this Curious Consultant, I’ll be making a conscious effort henceforth to make sure parameter order is lined up with the most used first, so I can deflect the finger pointing performance issues as appropriate. 🙂
The Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
using System; using System.Collections.Generic; using System.Collections; using System.Collections.Concurrent; using System.Data; using System.Data.Sql; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Threading; using System.Diagnostics; namespace TestApplication { class Program { static void Main(string[] args) { DateTime end; DateTime start = DateTime.Now; Console.WriteLine("### Overall Start Time: " + start.ToLongTimeString()); Console.WriteLine(); sTestIfParameterOrderMakesADifference(250); TestIfParameterOrderMakesADifference(500); TestIfParameterOrderMakesADifference(5000); TestIfParameterOrderMakesADifference(25000); end = DateTime.Now; Console.WriteLine(); Console.WriteLine("### Overall End Time: " + end.ToLongTimeString()); Console.WriteLine("### Overall Run Time: " + (end - start)); Console.WriteLine(); Console.WriteLine("Hit Enter to Exit"); Console.ReadLine(); } //#################################################### //Tests calling methods with parameters to see if parameter order //makes a difference in performance //https://www.dotnetperls.com/method-parameter //https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1809-avoid-excessive-locals?view=vs-2019 static void TestIfParameterOrderMakesADifference(int numberOfTimesToCallMethod) { Console.WriteLine("######## " + System.Reflection.MethodBase.GetCurrentMethod().Name); Console.WriteLine("Number of times methods will be called: " + numberOfTimesToCallMethod.ToString("#,##0")); Stopwatch sw = new Stopwatch(); DateTime end = DateTime.Now; DateTime start = DateTime.Now; int totalSum = 0; //Compiled first with this code uncommented //Compiled second with this code commented out. Console.WriteLine("###########################################################"); Console.WriteLine("TestIfParameterOrderMakesADifferenceSub1 test " + numberOfTimesToCallMethod.ToString("#,##0") + " at: " + DateTime.Now.ToLongTimeString()); sw.Restart(); for (int x = 0; x < numberOfTimesToCallMethod; x++) { totalSum += TestIfParameterOrderMakesADifferenceSub1(1, 2, true, true, 1, 2); } sw.Stop(); Console.WriteLine("Finished at: " + DateTime.Now.ToLongTimeString()); Console.WriteLine("Time to run: " + sw.Elapsed.ToString("mm\\:ss\\.fffffff")); Console.WriteLine("Sum is: " + totalSum.ToString("#,##0")); totalSum = 0; Thread.Sleep(500); //Compiled first with this code commented out. //compiled second with this code uncommented. //Console.WriteLine("###########################################################"); //Console.WriteLine("TestIfParameterOrderMakesADifferenceSub2 test " + numberOfTimesToCallMethod.ToString("#,##0") + " at: " + DateTime.Now.ToLongTimeString()); //sw.Restart(); //for (int x = 0; x < numberOfTimesToCallMethod; x++) //{ // totalSum += TestIfParameterOrderMakesADifferenceSub2(1, 2, true, true, 1, 2); //} //sw.Stop(); //Console.WriteLine("Finished at: " + DateTime.Now.ToLongTimeString()); //Console.WriteLine("Time to run: " + sw.Elapsed.ToString("mm\\:ss\\.fffffff")); //Console.WriteLine("Sum is: " + totalSum.ToString("#,##0")); //Thread.Sleep(500); Console.WriteLine(); } // This method tests the first two parameters in the loop. static int TestIfParameterOrderMakesADifferenceSub1(int a, int b, bool c, bool d, int e, int f) { int sum = 0; for (int x = 2; x < 10000000; x++) { if (a == x) { sum += 1; } if (b == x) { sum += 1; } } if (!c || !d || e == 10 || f == 10) { sum += 2; } return sum; } // This method tests the last two parameters in the loop. static int TestIfParameterOrderMakesADifferenceSub2(int a, int b, bool c, bool d, int e, int f) { int sum = 0; for (int x = 2; x < 10000000; x++) { if (e == x) { sum += 1; } if (f == x) { sum += 1; } } if (a == 10 || b == 10 || !c || !d) { sum += 2; } return sum; } } //class } //namespace |