Optimize Enumerable.SkipLast() for IPartition and IList#41342
Optimize Enumerable.SkipLast() for IPartition and IList#41342stephentoub merged 5 commits intodotnet:masterfrom
Conversation
|
Can you provide some pre and post performance figures using BenachmarkDotNet? There have been a number of suggestions to change the behaviour of linq query methods which were rejected because they degrade the performance of the common case in favour of an edge case, |
@Wraith2 Performance test is in the issue #40014. Should I move the testcase to here? |
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.915 (1803/April2018Update/Redstone4)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
Frequency=1945313 Hz, Resolution=514.0561 ns, Timer=TSC
.NET Core SDK=3.0.100
[Host] : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
|
|
Thank you @timandy. The benchmarks test results you provided show improvements. LGTM but also would like to get @stephentoub 's feedback on this. @adamsitnik would you recommend benchmark tests like these get included in the dotnet benchmark repo? |
|
For the benchmarks, the tests shared show the optimized case. Can you share results when the input doesn't hit the optimizations, namely when the input isn't an IList or an IPartition? For example, your setup does this: [GlobalSetup]
public void Setup()
{
List<int> tmp = new List<int>(COUNT);
for (int i = 0; i < COUNT; i++)
{
tmp.Add(i);
}
data = tmp;
}What are the results of your test if you instead do this: [GlobalSetup]
public void Setup()
{
data = GetInput(COUNT);
}
private static IEnumerable<int> GetInput(int count)
{
for (int i = 0; i < count; i++) yield return i;
}? Thanks. |
|
@stephentoub I test the code with your testcase, The result is BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.915 (1803/April2018Update/Redstone4)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
Frequency=1945313 Hz, Resolution=514.0561 ns, Timer=TSC
.NET Core SDK=3.0.100
[Host] : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
|
Thanks for sharing the results. Do we have any evidence around the typical type and length of input used with SkipLast? As expected, this demonstrates a nice improvement for the optimized case, but we pay for that with a non-trivial regression (20%) in the case of a short regular enumerable. |
This reverts commit 7c7908f
|
Other than my question in #41342 (comment), LGTM. |
|
@stephentoub I rerun the testcase several times, and wait the programe exit without do anything on my computer. About 5%-6% slower than before optimization. BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.915 (1803/April2018Update/Redstone4)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
Frequency=1945313 Hz, Resolution=514.0561 ns, Timer=TSC
.NET Core SDK=3.0.100
[Host] : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
|
|
@maryamariyan What should I do next? |
|
Thanks a lot for the PR @timandy. In other words, it would be hard to merge the PR if there is no evidence demonstrating that for the "most used cases" we have not made a regression. (The question posed in @stephentoub 's last comment needs to be answered before we can confidently merge the PR). |
|
I think it's worthy to use 5% loss of the performance swap 60% performance improvement. And the time of one option for small amount is very very short. |
Waving my hands, if the 5% regression is for a case that's > 12x as common as the 60% improvement, this is a net loss. What evidence do we have about how common these cases are? That's what I'm trying to understand. |
|
@timandy are you planning on following up on this wrt the question asked above? |
|
Ok. I just looked at what we do for Enumerable.Skip, Take, and TakeLast, and for all of those we check both of these interfaces. Given that, it seems reasonable to be consistent and complete this one with those two interface as well. I'd like to avoid another PR that does the same in other operators without compelling data about why it's worthwhile. Thanks. |
…x#41342) * Optimize Enumerable.SkipLast() for IPartition and IList * Convert to conditional expression * Add parameter name * Revert "Convert to conditional expression" This reverts commit 7c7908fb * Format conditional expression Commit migrated from dotnet/corefx@bdd3397
Enumerable.SkipLast() can be cast to Enumerable.Take() when source type is IPartition or IList.
performance test see #40014