Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

And linq #895

Merged
merged 4 commits into from
Sep 30, 2024
Merged

And linq #895

merged 4 commits into from
Sep 30, 2024

Conversation

philstopford
Copy link
Contributor

With LINQ on top, benchmark doesn't show any performance regression (and any win may also be purely based on the circumstances). It looks like a wash overall, but it makes the code a little cleaner to my eye and that could be a good thing.

This is a partial set of clean-ups. There could be others, but they may be controversial (e.g. adopting LINQ in some areas, which may or may not hurt performance). I've also tried to be mindful of target framework versions and also some edge cases (e.g. hashing) where seemingly redundant chunks of code, if removed or changed for another option, may blow things up.

See what you think.
Pre-change:

// * Summary *

BenchmarkDotNet=v0.12.1, OS=endeavouros
AMD Ryzen 7 5800H with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=8.0.108
  [Host]     : .NET Core 8.0.8 (CoreCLR 8.0.824.36612, CoreFX 8.0.824.36612), X64 RyuJIT
  Job-GRIUMJ : .NET Core 8.0.8 (CoreCLR 8.0.824.36612, CoreFX 8.0.824.36612), X64 RyuJIT

IterationCount=1  LaunchCount=1  WarmupCount=1

|         Method | EdgeCount |       Mean | Error |     Gen 0 |     Gen 1 |     Gen 2 | Allocated |
|--------------- |---------- |-----------:|------:|----------:|----------:|----------:|----------:|
| Intersection_N |      1000 |   119.9 ms |    NA | 1000.0000 |         - |         - |   9.04 MB |
| Intersection_N |      2000 |   542.1 ms |    NA | 1000.0000 |         - |         - |   16.2 MB |
| Intersection_N |      3000 | 1,431.1 ms |    NA | 5000.0000 | 4000.0000 | 1000.0000 |  37.28 MB |
| Intersection_N |      4000 | 3,008.9 ms |    NA | 6000.0000 | 5000.0000 | 1000.0000 |  47.41 MB |
| Intersection_N |      5000 | 6,173.7 ms |    NA | 9000.0000 | 8000.0000 | 1000.0000 |  74.05 MB |

// * Legends *
  EdgeCount : Value of the 'EdgeCount' parameter
  Mean      : Arithmetic mean of all measurements
  Error     : Half of 99.9% confidence interval
  Gen 0     : GC Generation 0 collects per 1000 operations
  Gen 1     : GC Generation 1 collects per 1000 operations
  Gen 2     : GC Generation 2 collects per 1000 operations
  Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 ms      : 1 Millisecond (0.001 sec)

// * Diagnostic Output - MemoryDiagnoser *

// ***** BenchmarkRunner: End *****
// ** Remained 0 benchmark(s) to run **
Run time: 00:00:47 (47.83 sec), executed benchmarks: 5

Global total time: 00:00:57 (57.28 sec), executed benchmarks: 5

Post-change:

// * Summary *

BenchmarkDotNet=v0.12.1, OS=endeavouros
AMD Ryzen 7 5800H with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=8.0.108
  [Host]     : .NET Core 8.0.8 (CoreCLR 8.0.824.36612, CoreFX 8.0.824.36612), X64 RyuJIT
  Job-WWTCPL : .NET Core 8.0.8 (CoreCLR 8.0.824.36612, CoreFX 8.0.824.36612), X64 RyuJIT

IterationCount=1  LaunchCount=1  WarmupCount=1

|         Method | EdgeCount |       Mean | Error |     Gen 0 |     Gen 1 |     Gen 2 | Allocated |
|--------------- |---------- |-----------:|------:|----------:|----------:|----------:|----------:|
| Intersection_N |      1000 |   127.1 ms |    NA |         - |         - |         - |   6.85 MB |
| Intersection_N |      2000 |   475.1 ms |    NA | 2000.0000 | 1000.0000 |         - |  23.04 MB |
| Intersection_N |      3000 | 1,408.1 ms |    NA | 5000.0000 | 4000.0000 | 1000.0000 |  36.15 MB |
| Intersection_N |      4000 | 2,874.6 ms |    NA | 5000.0000 | 4000.0000 | 1000.0000 |  41.76 MB |
| Intersection_N |      5000 | 5,954.3 ms |    NA | 9000.0000 | 8000.0000 | 2000.0000 |  68.62 MB |

// * Legends *
  EdgeCount : Value of the 'EdgeCount' parameter
  Mean      : Arithmetic mean of all measurements
  Error     : Half of 99.9% confidence interval
  Gen 0     : GC Generation 0 collects per 1000 operations
  Gen 1     : GC Generation 1 collects per 1000 operations
  Gen 2     : GC Generation 2 collects per 1000 operations
  Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 ms      : 1 Millisecond (0.001 sec)

// * Diagnostic Output - MemoryDiagnoser *

// ***** BenchmarkRunner: End *****
// ** Remained 0 benchmark(s) to run **
Run time: 00:00:46 (46.34 sec), executed benchmarks: 5

Global total time: 00:00:51 (51.28 sec), executed benchmarks: 5
@AngusJohnson
Copy link
Owner

Are there any downsides to using LINQ (apart from the potential to impact performance that your tests have pretty much discounted)? If there aren't, I'd be happy to merge.

@philstopford
Copy link
Contributor Author

No real disadvantages that I can see. It's not an automatic win/loss on performance, hence the test data being useful in making a suggestion about this change. I also saw no measurable impact from these changes overall in my end-user applications based around Clipper.

In the case of this library, the LINQ candidates are pretty straightforward to understand. I admit that I often decline LINQ single-liners that others would go for, just because they are, to me, not easily readable, but here I had no concerns.

LINQ can also be a little tricky to debug, if you use it like a hammer, but here, I don't really see a concern, based on how and where it is used. Errors can be raised and linked back to a different part of the code and that needs to be born in mind.

There's a fairly decent summary here : https://stackoverflow.com/questions/271384/pros-and-cons-of-linq-language-integrated-query

@AngusJohnson
Copy link
Owner

AngusJohnson commented Sep 30, 2024

Thanks Phil. I really appreciate what you've done.

Edit: I note that there's a conflict that's stopping the merge. Are you able to resolve this?

@philstopford
Copy link
Contributor Author

philstopford commented Sep 30, 2024

Thanks Phil. I really appreciate what you've done.

Edit: I note that there's a conflict that's stopping the merge. Are you able to resolve this?

Looks trivial - I'll sort it out on my end (collision between the clean-up merge and this PR) and update shortly. Stay tuned.

@AngusJohnson AngusJohnson merged commit 907d1fd into AngusJohnson:main Sep 30, 2024
7 checks passed
@philstopford philstopford deleted the andLINQ branch October 1, 2024 01:12
if (pt.y < bounds.top) bounds.top = pt.y;
if (pt.y > bounds.bottom) bounds.bottom = pt.y;
}
foreach (PointD pt in from pi in PolyInfoList from path in pi.paths from pt in path select pt)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much less easy to debug now.

@JimBobSquarePants
Copy link

I don't mean to rain on anyones parade here, but I would introduce LINQ into high performance scenarios as an absolute last resort. There are often hidden allocations that are hard to spot plus debugging of stements becomes less trivial in complex sections (see highlighted comment).

While no change was seen in the current benchmarks there's no guarantee that the benchmarks themseleves provide adequate coverage.

@AngusJohnson
Copy link
Owner

AngusJohnson commented Oct 1, 2024

Thanks for the feedback JBSP. I have a low threshold to reverting the linq dependency.

@@ -382,12 +366,11 @@ public static Path64 ScalePath(Path64 path, double scale)
{
if (InternalClipper.IsAlmostZero(scale - 1)) return path;
Path64 result = new Path64(path.Count);
result.AddRange(path.Select(pt => new Point64(pt.X * scale, pt.Y * scale)));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right; The statement was moved outside of the !USINGZ block but the case for USINGZ is still there.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, its a bug.

@AngusJohnson
Copy link
Owner

I've now reversed the LINQ dependency.

@philstopford
Copy link
Contributor Author

I ran a suite of tests using applications that make heavy use of Clipper and saw no performance impact. I expect I also had some clean-up code in my use cases that stripped duplicate vertices. It might be wise to have test coverage for the duplicates, though, to avoid similar 'regressions' in future - reverting the PR is fine, but the door is still wide open to a similar regression in future.

@AngusJohnson
Copy link
Owner

HI Phil. I really appreciate your efforts to improve the library.
I had a low threshold to reverse your linq dependency because I personally found it harder to read the code.
But my knowledge of C# is extremely limited, so I'm very dependant on feedback from the library's C# users.
I'm sorry if I reverted some cleanup code too. I thought I'd only reverted the linq specific changes.
Anyhow, I'm not sure what 'duplicates' or 'regressions' you're referring to.

@philstopford
Copy link
Contributor Author

No worries - LINQ is always something of a love/hate/indifference thing, and people tend to avoid it also on the assumption it will mess up performance (which I could find no case where it impacted performance in my tests). My comment about the regression was for the now-reverted change in line 369. I use USINGZ quite a bit and it never flagged on my side and perhaps there needs to be some test coverage to avoid a similar mistake in future. Failing the test would have avoided the oversight, I suspect.

@AngusJohnson
Copy link
Owner

I use USINGZ quite a bit and it never flagged on my side and perhaps there needs to be some test coverage to avoid a similar mistake in future.

Yep, I'd be happy to add more CI testing (including C#).
But I'm not overly motivated to doing this myself, especially for C#, since my focus is primarily on ensuring the C++ code is robust (in every way).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants