Skip to content

perf(Core): Avoid boxing tuple comparisons - #879

Merged
knocte merged 1 commit into
fsprojects:masterfrom
Numpsy:tuple_compre
Aug 6, 2026
Merged

perf(Core): Avoid boxing tuple comparisons#879
knocte merged 1 commit into
fsprojects:masterfrom
Numpsy:tuple_compre

Conversation

@Numpsy

@Numpsy Numpsy commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

…stead of >=

The existing code appears to be using boxing comparisons which generate massive amounts of allocations. Changing it to use struct tuples and CompareTo avoids that, giving a large drop in allocations and a measurable performance gain.

@Numpsy

Numpsy commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

An experiment based on these profiling numbers:

image image

Which suggest that

  1. The function generates a huge amount of reference tuples, which have quite a significant effect of memory usage
  2. The implementation of >= that it uses is boxing all of the integers before comparing them, which also generates a large amount of memory allocations

So, see if that can be improved by

  1. Using ValueTuple instead of Tuple
  2. Directly calling CompareTo, so it uses the variant that works directly on integers without boxing.

In my local testing, I get this when running the benchmark project on the latest master branch, using the set of rules that the CI uses for the extended self check

| Method         | Mean    | Error    | StdDev   | Gen0       | Gen1       | Gen2      | Allocated |
|--------------- |--------:|---------:|---------:|-----------:|-----------:|----------:|----------:|
| LintParsedFile | 1.402 s | 0.0123 s | 0.0109 s | 31000.0000 | 10000.0000 | 2000.0000 | 518.02 MB |

But with this change I get

| Method         | Mean    | Error    | StdDev   | Gen0       | Gen1      | Gen2      | Allocated |
|--------------- |--------:|---------:|---------:|-----------:|----------:|----------:|----------:|
| LintParsedFile | 1.282 s | 0.0041 s | 0.0032 s | 15000.0000 | 6000.0000 | 2000.0000 | 263.96 MB |

Which is a pretty big improvement for a two line change.

This could maybe be tuned with different >= / <= implementations of a more direct approach to comparing the ranges, but putting this here now to show the effect anyway.

@Numpsy
Numpsy marked this pull request as draft August 3, 2026 11:32
@knocte

knocte commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Looks great indeed, @webwarrior-ws can you review?

@webwarrior-ws

Copy link
Copy Markdown
Contributor

Makes sense. It's strange that F# compiler didn't optimize away tuple allocations here.

The existing tuple comparison code
1) Allocates large numbers of reference tuples
2) Uses boxing comparisons which allocate large numbers of
   integers

Explicitly using struct tuples and CompareTo avoids
all of these allocations.

Benchmarks before, using the extended set of selfcheck rules

| Method         | Mean    | Error    | StdDev   | Gen0       | Gen1       | Gen2      | Allocated |
|--------------- |--------:|---------:|---------:|-----------:|-----------:|----------:|----------:|
| LintParsedFile | 1.402 s | 0.0123 s | 0.0109 s | 31000.0000 | 10000.0000 | 2000.0000 | 518.02 MB |

After

| Method         | Mean    | Error    | StdDev   | Gen0       | Gen1      | Gen2      | Allocated |
|--------------- |--------:|---------:|---------:|-----------:|----------:|----------:|----------:|
| LintParsedFile | 1.282 s | 0.0041 s | 0.0032 s | 15000.0000 | 6000.0000 | 2000.0000 | 263.96 MB |
@Numpsy

Numpsy commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

It's strange that F# compiler didn't optimize away tuple allocations here.

I had thought that the memory optimization features in .NET 9 that automatically convert local allocations to stack allocations might apply here, but it seems not (and running the benchmarks on .NET 10 or the 11 preview doesn't either). Maybe pushing the tuples through IComparer as Object means it can't, I'm not sure.

@Numpsy Numpsy changed the title Experiment(rangeContainsOtherRange): Compare tuples with CompareTo in… perf(Core): Avoid boxing tuple comparisons Aug 4, 2026
@Numpsy
Numpsy marked this pull request as ready for review August 4, 2026 21:09
@knocte
knocte merged commit ea530b8 into fsprojects:master Aug 6, 2026
8 checks passed
@Numpsy
Numpsy deleted the tuple_compre branch August 7, 2026 09:00
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.

3 participants