I need to guess, but I'm still doing it. I think we should try to get rid of the embedded lambda material and delegate conversions. I'm not sure how good this is. The sort function should be like this:
Func<int, int, int>[] sorters = ...; //fill this. it really should be an array! Comparison<int> = (a, b) => { foreach (var s in sorters) { var cmp = s(a, b); if(cmp != 0) return cmp; } return 0; };
So, we got rid of nested calls. All simple loop. You can create specialized versions for small loop sizes:
Func<int, int, int>[] sorters = ...; //fill this. it really should be an array! switch (sorters.Length) { case 2: { var s0 = sorters[0], s1 = sorters[1]; Comparison<int> = (a, b) => { var cmp = s0(a, b); if(cmp != 0) return cmp; var cmp = s1(a, b); if(cmp != 0) return cmp; return 0; }; }
Expand the loop so that no arrays are displayed anymore during sorting.
All this really works around the fact that we do not have static knowledge about the sorting structure. This would be much faster if the comparison function were simply transmitted by the caller.
Update: Repro (100% more than LINQ)
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Func<int, int, int>[] sorters = new Func<int, int, int>[] { (a, b) => (a & 0x1).CompareTo(b & 0x1), (a, b) => (a & 0x2).CompareTo(b & 0x2), (a, b) => (a & 0x4).CompareTo(b & 0x4), (a, b) => a.CompareTo(b), }; Func<int, int, int> comparisonB = sorters[0]; for (int i = 1; i < sorters.Length; i++) { var func1 = comparisonB; var func2 = sorters[i]; comparisonB = (a, b) => { var cmp = func1(a, b); if (cmp != 0) return cmp; return func2(a, b); }; } var comparisonC = new Comparison<int>(comparisonB); Comparison<int> comparisonA = (a, b) => { foreach (var s in sorters) { var cmp = s(a, b); if (cmp != 0) return cmp; } return 0; }; Func<int, int, int> s0 = sorters[0], s1 = sorters[1], s2 = sorters[2], s3 = sorters[3]; Comparison<int> comparisonD = (a, b) => { var cmp = s0(a, b); if (cmp != 0) return cmp; cmp = s1(a, b); if (cmp != 0) return cmp; cmp = s2(a, b); if (cmp != 0) return cmp; cmp = s3(a, b); if (cmp != 0) return cmp; return 0; }; { GC.Collect(); var data = CreateSortData(); var sw = Stopwatch.StartNew(); Array.Sort(data, comparisonC); sw.Stop(); Console.WriteLine(sw.Elapsed.TotalSeconds); } { GC.Collect(); var data = CreateSortData(); var sw = Stopwatch.StartNew(); Array.Sort(data, comparisonA); sw.Stop(); Console.WriteLine(sw.Elapsed.TotalSeconds); } { GC.Collect(); var data = CreateSortData(); var sw = Stopwatch.StartNew(); Array.Sort(data, comparisonD); sw.Stop(); Console.WriteLine(sw.Elapsed.TotalSeconds); } { GC.Collect(); var data = CreateSortData(); var sw = Stopwatch.StartNew(); foreach (var source in data.OrderBy(x => x & 0x1).ThenBy(x => x & 0x2).ThenBy(x => x & 0x4).ThenBy(x => x)) { } sw.Stop(); Console.WriteLine(sw.Elapsed.TotalSeconds); }