Skip to content

Conversation

@manofstick
Copy link
Contributor

@manofstick manofstick commented Nov 27, 2019

2nd of n optimizations lifted from my exploration of an alternative System.Linq implementation Cistern.Linq. There are lots more goodies there, but only so many that will survive the transfer to System.Linq!

(Previous corefx optimization)

This one (to a first order approximation) adds O(N) operations to the sort (huh?) and also forces dual arrays to be sorted (via Array.Sort(keys, elements, ...)) so that doesn't sound very good now does it? (Of course it remains a stable sort)

But...

  • Simplifies comparer (uses provided one directly, rather than wrapping and accessing keys indirectly via array indexing)
  • Uses Array.Sort directly with provided comparer, which is optimized for the default comparer for primitives (and possibly get an extra kick in the pants - come on @damageboy !)
  • Benefits from better localization of data, thus better caching

Anyway, here are some results from a slightly modified performance test that just uses a variety of sorting sizes gives the following results:

Slower diff/base Base Median (ns) Diff Median (ns) Modality
System.Linq.Tests.Perf_OrderBy.OrderByString(NumberOfPeople: 1) 1.09 205.93 224.48
System.Linq.Tests.Perf_OrderBy.OrderByString(NumberOfPeople: 2) 1.09 320.09 347.86
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 1) 1.05 189.73 200.04
System.Linq.Tests.Perf_OrderBy.OrderByString(NumberOfPeople: 4) 1.05 616.64 648.56
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 2) 1.05 275.59 289.65
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 8) 1.04 1121.75 1164.04
Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 256) 2.84 26344.39 9262.66
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 128) 2.44 11127.33 4561.05
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 64) 2.14 4858.36 2271.90
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 16) 1.80 1287.39 713.56
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 32) 1.77 2087.83 1180.17
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 128) 1.21 39562.96 32765.85
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 8) 1.19 466.95 392.36
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 256) 1.16 81546.65 70346.17
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 2) 1.06 226.88 213.39
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 32) 1.06 6137.20 5775.37
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 0) 1.05 77.19 73.31
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 16) 1.05 2973.10 2841.96
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 0) 1.04 75.35 72.18
System.Linq.Tests.Perf_OrderBy.OrderByCustomComparer(NumberOfPeople: 64) 1.03 15992.17 15479.79
System.Linq.Tests.Perf_OrderBy.OrderByValueType(NumberOfPeople: 1) 1.03 171.79 167.52

So there is some degraded performance for non-value types for very small collections, but these are minor, and only for < 10 elements - due to additional overhead of checking type, etc., but the performance of a known value type (DateTime) is significant - ~35% of the time for 256 elements.

A value type which has a custom comparer also benefits (although by a much more modest amount). (i.e. not just limited to the case of Array Sort optimizing the primitive types)

But!! If the comparer was significantly complex then this could slow it down (due to the additional O(N) operations) but as N increases, the ~O(N log N) will drown this out.

... and this really isn't just for value types as N grows it overcomes the additional O(N) and has faster results on reference types (well I tried it with the name strings as per System.Linq.Tests.Perf_OrderBy.OrderByString and around the few hundred items it was faster). But the current state of this PR is just for value types.

@maryamariyan maryamariyan added the tenet-performance Performance related issue label Nov 27, 2019
@stephentoub
Copy link
Member

@manofstick, thanks for the PRs. I just returned from vacation. I will review your two PRs hopefully this week.

@manofstick
Copy link
Contributor Author

@stephentoub - No rush; just seeking an update...

@stephentoub
Copy link
Member

stephentoub commented Jun 8, 2020

@manofstick, I'm very sorry for the super-long delay on this one; sincere apologies.

In the end, I think we need to go ahead and close it. I was keeping it open in hopes I could convince myself that we should take it, and it's great that it makes the common case with many elements faster, but it's pretty common with LINQ to process short enumerables and to do so repeatedly, and so a regression in a short list in favor of improvements in a long list isn't necessarily a good tradeoff. But really the clincher for me is the long discussion that led to #36643, where multiple folks in the community expressed strong concern at adding an O(N) amount of work even when doing so was part of removing an O(N log N) amount of work; this has a very similar feel to it, and I don't think the rewards associated with this PR are high enough to want to take such a risk again.

Thank you, again, for all of your efforts here.

cc: @eiriktsarpalis, @adamsitnik

@stephentoub stephentoub closed this Jun 8, 2020
@manofstick
Copy link
Contributor Author

@stephentoub no worries :-) pretty hard to push anything through when there is a mature code base!

I mean I'm not 100% convinced that the arguments from #36643 really are relevant here (i.e. the predicate in that case was being used in places where it previously wasn't being used - i.e. slurped up into being called on all elements, rather than just the front elements of a sorted list - i.e. .net frameworks use has a deterministic state, i.e. the sorted first n elements until an event, but in PR it's just being used as an existing sort predicate where any assumptions of use by the client shouldn't exist (Otherwise any change to the sort algo would destroy clients, and I believe in the move to .net core as it moved from a vanilla quick sort to a slight modification, hence changing the potential order of the execution of the predicate, but that didn't cause issues)...

...but regardless, I can easily see from your perspective why this is a risk that you are unwilling to take.

Thanks for taking the time to have a look - who knows, I might even hit you with another PR soon :-P ! (there is one more from Cistern that I think could stand a chance!!)

@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Linq tenet-performance Performance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants