static CountedStopwatch Time_Foreach_YieldEnumerator(ReadOnlyCollectionYieldEnumerator <string> list) { var timer = new CountedStopwatch(TestName("foreach", list.GetType())); for (int i = 0; i < TimedIntervals; i++) { timer.Start(); foreach (var item in list) { DontElide(item); } timer.Stop(); } return(timer); }
static void Main(string[] args) { // Data setup: var list = new List <string>(capacity: CollectionSize); for (int i = 0; i < list.Capacity; i++) { list.Add(NextString()); } var array = list.ToArray(); var refList = new ReadOnlyCollectionRefEnumerator <string>(list); var valList = new ReadOnlyCollectionValEnumerator <string>(list); var yieldList = new ReadOnlyCollectionYieldEnumerator <string>(list); // warm up QPC. I've noticed the first use can have a small delay, so // make sure we don't get that while gathering measurements. { var warmup = new System.Diagnostics.Stopwatch(); warmup.Start(); warmup.Restart(); warmup.Restart(); } // test cases: Func <CountedStopwatch>[] tests = { // array tests: () => Time_Foreach_Array(array), () => Time_For_Array(array), () => Time_Foreach_IList(array), () => Time_For_IList(array), // List<T> tests: () => Time_Foreach_List(list), () => Time_For_List(list), () => Time_Foreach_IList(list), () => Time_For_IList(list), // Ref enumerator tests: () => Time_Foreach_RefEnumerator(refList), () => Time_For_RefEnumerator(refList), () => Time_Foreach_IList(refList), () => Time_For_IList(refList), // Val enumerator tests: () => Time_Foreach_ValEnumerator(valList), () => Time_For_ValEnumerator(valList), () => Time_Foreach_IList(valList), () => Time_For_IList(valList), // Yield enumerator tests: () => Time_Foreach_YieldEnumerator(yieldList), () => Time_For_YieldEnumerator(yieldList), () => Time_Foreach_IList(yieldList), () => Time_For_IList(yieldList), #region Generic caller (IList<string> constraint) // While trying to clean up the tests (less duplicate code and such) I made // these generic TimeForeach and TimeFor functions. However, I found that // they has *significantly* worse time-performance than the non-generic // equivalents, and in turn, make for interesting, though unique, test cases. // // Later inspection revealed that the IL emitted for the generic methods has // callvirt instructions to IList<string> members. // array tests: () => TimeForeach(array), () => TimeFor(array), // List<T> tests: () => TimeForeach(list), () => TimeFor(list), // Ref enumerator tests: () => TimeForeach(refList), () => TimeFor(refList), // Val enumerator tests: () => TimeForeach(valList), () => TimeFor(valList), // Yield enumerator tests: () => TimeForeach(yieldList), () => TimeFor(yieldList), #endregion #region IList<string> Hoisted count () => Time_For_IList_HoistedCount(array), () => Time_For_IList_HoistedCount(list), () => Time_For_IList_HoistedCount(refList), () => Time_For_IList_HoistedCount(valList), () => Time_For_IList_HoistedCount(yieldList), #endregion #region Trying to get generics to have better performance () => TimeForeach_ClassConstraint(array), () => TimeForeach_ClassConstraint_NullCheck(array), () => TimeFor_ClassConstraint(array), () => TimeFor_NullCheck(array), () => TimeFor_HoistedCount(array), () => TimeFor_ClassConstraint_NullCheck(array), () => TimeFor_ClassConstraint_NullCheck_HoistedCount(array), #endregion #region Dynamic () => TimeForeach_Dynamic(array), () => TimeForeach_Dynamic(list), () => TimeForeach_Dynamic(refList), () => TimeForeach_Dynamic(valList), () => TimeForeach_Dynamic(yieldList), () => TimeFor_Dynamic(array), () => TimeFor_Dynamic(list), () => TimeFor_Dynamic(refList), () => TimeFor_Dynamic(valList), () => TimeFor_Dynamic(yieldList), #endregion }; // execution + output: string sepLine = new string('=', 80); foreach (var testCase in tests) { // try to avoid running anything GC-related while timing things. GC.Collect(); GC.WaitForPendingFinalizers(); // run the test case CountedStopwatch result = testCase(); // print the test result Console.WriteLine(sepLine); Console.WriteLine("Test: " + result.TestName); Console.WriteLine(); Console.WriteLine("# Intervals: " + result.Intervals); Console.WriteLine("Total Time: " + result.Elapsed.TotalMilliseconds + " ms"); Console.WriteLine("Average Time: " + result.AverageElapsed.TotalMilliseconds + " ms"); Console.WriteLine(); Console.WriteLine(); } // pause if we are writing to the console if (!Console.IsOutputRedirected) { Console.WriteLine("Press any key to continue . . ."); Console.ReadKey(intercept: true); } }