Skip to content

Avoid redundant boxing in ValueTuple<T1,...,T7,TRest>#128814

Merged
MihaZupan merged 1 commit into
dotnet:mainfrom
prozolic:valuetuple
Jun 1, 2026
Merged

Avoid redundant boxing in ValueTuple<T1,...,T7,TRest>#128814
MihaZupan merged 1 commit into
dotnet:mainfrom
prozolic:valuetuple

Conversation

@prozolic
Copy link
Copy Markdown
Contributor

This PR use pattern variable capture (is IValueTupleInternal rest) instead of a separate type-check followed by a cast to IValueTupleInternal. This avoids a redundant boxing of TRest when it is a ValueTuple<TRest> containing reference-type fields, reducing heap allocations in GetHashCode, ToString, ITuple.Length, and the ITuple indexer.

Benchmark

Method Job Toolchain Mean Error StdDev Median Min Max Ratio RatioSD Gen0 Allocated Alloc Ratio
GetHashCode_Int8 Job-QIDXWP \main\corerun.exe 5.260 ns 0.1494 ns 0.1720 ns 5.318 ns 4.939 ns 5.557 ns 1.00 0.00 - - NA
GetHashCode_Int8 Job-TLPBEL \pr\corerun.exe 4.964 ns 0.1389 ns 0.1599 ns 4.911 ns 4.764 ns 5.344 ns 0.94 0.04 - - NA
GetHashCode_Str8 Job-QIDXWP \main\corerun.exe 13.155 ns 0.5284 ns 0.5873 ns 13.028 ns 12.442 ns 14.690 ns 1.00 0.00 0.0028 24 B 1.00
GetHashCode_Str8 Job-TLPBEL \pr\corerun.exe 8.563 ns 0.1916 ns 0.2206 ns 8.447 ns 8.311 ns 8.969 ns 0.65 0.03 - - 0.00
ToString_Int8 Job-QIDXWP \main\corerun.exe 72.434 ns 2.1456 ns 2.4709 ns 71.774 ns 69.573 ns 76.946 ns 1.00 0.00 0.0304 256 B 1.00
ToString_Int8 Job-TLPBEL \pr\corerun.exe 70.706 ns 1.8968 ns 2.1844 ns 70.259 ns 67.266 ns 75.064 ns 0.98 0.04 0.0305 256 B 1.00
ToString_Str8 Job-QIDXWP \main\corerun.exe 76.118 ns 2.1219 ns 2.4436 ns 76.160 ns 71.786 ns 81.114 ns 1.00 0.00 0.0334 280 B 1.00
ToString_Str8 Job-TLPBEL \pr\corerun.exe 71.267 ns 1.5937 ns 1.8353 ns 70.953 ns 68.286 ns 74.699 ns 0.94 0.04 0.0305 256 B 0.91
Length_Int8 Job-QIDXWP \main\corerun.exe 4.052 ns 0.1157 ns 0.1287 ns 4.069 ns 3.882 ns 4.256 ns 1.00 0.00 0.0057 48 B 1.00
Length_Int8 Job-TLPBEL \pr\corerun.exe 4.284 ns 0.1248 ns 0.1438 ns 4.287 ns 4.080 ns 4.595 ns 1.06 0.05 0.0057 48 B 1.00
Length_Str8 Job-QIDXWP \main\corerun.exe 10.711 ns 0.2865 ns 0.3184 ns 10.782 ns 10.178 ns 11.242 ns 1.00 0.00 0.0095 80 B 1.00
Length_Str8 Job-TLPBEL \pr\corerun.exe 6.129 ns 0.1166 ns 0.1248 ns 6.155 ns 5.816 ns 6.290 ns 0.57 0.02 0.0067 56 B 0.70
Indexer_Int8 Job-QIDXWP \main\corerun.exe 6.039 ns 0.2171 ns 0.2500 ns 6.036 ns 5.719 ns 6.515 ns 1.00 0.00 0.0086 72 B 1.00
Indexer_Int8 Job-TLPBEL \pr\corerun.exe 6.479 ns 0.2155 ns 0.2395 ns 6.487 ns 6.112 ns 7.022 ns 1.07 0.06 0.0086 72 B 1.00
Indexer_Str8 Job-QIDXWP \main\corerun.exe 11.339 ns 0.2880 ns 0.3201 ns 11.413 ns 10.752 ns 11.848 ns 1.00 0.00 0.0095 80 B 1.00
Indexer_Str8 Job-TLPBEL \pr\corerun.exe 6.356 ns 0.1778 ns 0.1976 ns 6.367 ns 6.058 ns 6.811 ns 0.56 0.02 0.0067 56 B 0.70
    public class Perf_ValueTuple
    {
        private ValueTuple<int, int, int, int, int, int, int, ValueTuple<int>> _tupleInt8;
        private ValueTuple<int, int, int, int, int, int, int, ValueTuple<string>> _tupleStr8;

        [GlobalSetup]
        public void Setup()
        {
            _tupleInt8 = new(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int>(8));
            _tupleStr8 = new(1, 2, 3, 4, 5, 6, 7, new ValueTuple<string>("8"));
        }

        [Benchmark]
        public int GetHashCode_Int8() => _tupleInt8.GetHashCode();

        [Benchmark]
        public int GetHashCode_Str8() => _tupleStr8.GetHashCode();

        [Benchmark]
        public string ToString_Int8() => _tupleInt8.ToString();

        [Benchmark]
        public string ToString_Str8() => _tupleStr8.ToString();

        [Benchmark]
        public int Length_Int8() => ((ITuple)_tupleInt8).Length;

        [Benchmark]
        public int Length_Str8() => ((ITuple)_tupleStr8).Length;

        [Benchmark]
        public object Indexer_Int8() => ((ITuple)_tupleInt8)[7];

        [Benchmark]
        public object Indexer_Str8() => ((ITuple)_tupleStr8)[7];
    }

Use pattern variable capture (`is IValueTupleInternal rest`) instead
of a separate type-check followed by a cast to IValueTupleInternal.
This avoids a redundant boxing of TRest when it is a ValueTuple<TRest>
containing reference-type fields, reducing heap allocations
in GetHashCode, ToString, ITuple.Length, and the ITuple indexer.
Copilot AI review requested due to automatic review settings May 31, 2026 12:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Refactors ValueTuple.cs to use pattern matching with type-checked variable declarations, replacing redundant cast expressions after is checks.

Changes:

  • Replaces is IValueTupleInternal checks followed by ((IValueTupleInternal)Rest) casts with is IValueTupleInternal rest pattern matching.
  • Applies the change in GetHashCode, ToString, IValueTupleInternal.ToStringEnd, ITuple.Length, and the indexer.

@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 31, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label May 31, 2026
@prozolic prozolic marked this pull request as ready for review May 31, 2026 12:57
Copy link
Copy Markdown
Member

@EgorBo EgorBo left a comment

Choose a reason for hiding this comment

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

There is a known issue somewhere against JIT for this pattern around shared generics, but I think it's fine to accept this change anyway as it simplifies code.

@prozolic
Copy link
Copy Markdown
Contributor Author

Thank you for the review!
Just to add some context, ValueTuple<T1,...,T7,TRest> .GetHashCodeCore was already using pattern variable capture (is IValueTupleInternal rest). So I applied this partly to keep the pattern usage consistent.

@EgorBo EgorBo added area-System.Runtime and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Jun 1, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

@MihaZupan MihaZupan merged commit a82b355 into dotnet:main Jun 1, 2026
168 of 173 checks passed
@prozolic prozolic deleted the valuetuple branch June 1, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Runtime community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants