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

Add support for ISpanFormattable in StringBuilder.Append(Object) #92197

Closed
wants to merge 3 commits into from

Conversation

manandre
Copy link
Contributor

Closes #92193

Applying the optimization for ISpanFormattable, already implemented in AppendFormat, to Append.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Sep 17, 2023
@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Sep 17, 2023
@stephentoub
Copy link
Member

Applying the optimization for ISpanFormattable, already implemented in AppendFormat, to Append.

Note that AppendFormatted is different. It's generic, which means thanks to generic specialization, we don't pay this check for value types; it evaporates completely in optimized code when the T is a struct. That's not the case here for Append(object), which means we'll be paying for the interface check more often.

@stephentoub
Copy link
Member

Or we don't want to impact the performance when the object doesn't implement ISpanFormattable?

That's the concern. I'm not saying we can't do this, just that there is a cost associated with it, and we need to validate the perf on the common case where it doesn't implement the interface.

@manandre
Copy link
Contributor Author

I am going to setup a benchmark to evaluate such impact for use cases with and without objects implementing ISpanFormattable.
It seems it won't be easy as dotnet/performance still does not support .NET 9.

@manandre
Copy link
Contributor Author

manandre commented Oct 5, 2023

I have benchmarked the changes to the StringBuilder.Append(object?) method.
Benchmark code: dotnet/performance@main...manandre:performance:sb-append-object
Benchmark results (where not Same):

Method data Ratio MannWhitney(5%) Alloc Ratio Total Ratio
Append_ValueTypes 1.25 Slower 0.76 0.95
Append_Object null 4.03 Slower 1.00 4.03
Append_Object Char : A 1.11 Slower 0.21 0.23
Append_Object Int32 : 42 1.75 Slower 1.00 1.75
Append_Object Rune : A 0.98 Same 0.21 0.21
Append_Object String : 2.39 Slower 1.00 2.39
Append_Object String : 1234567890 1.28 Slower 1.00 1.28
Append_Object Version : 1.2.3.4 0.95 Same 0.56 0.53

We should maybe put the null check in first place in the switch order to reduce the impact of the change.
Globally, the duration impact is quite important but I do not know how to compare it against the allocation gain.
I have computed a Total ratio (= Ratio * Alloc Ratio) as a naive metric.
Moreover, it is difficult to estimate the "weight" of each use case...

@stephentoub Can you please help here?

Benchmark summary

BenchmarkDotNet v0.13.9-nightly.20230908.70, Windows 10 (10.0.19045.3448/22H2/2022Update)
Intel Core i7-4770K CPU 3.50GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.100-rc.1.23463.5
[Host] : .NET 8.0.0 (8.0.23.41904), X64 RyuJIT AVX2
Job-MABLYB : .NET 8.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-DFWZBP : .NET 8.0.0 (42.42.42.42424), X64 RyuJIT AVX2

PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true IterationTime=250.0000 ms
MaxIterationCount=20 MinIterationCount=15 WarmupCount=1

Method Job Toolchain repeat length data Mean Error StdDev Median Min Max Ratio MannWhitney(5%) RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
AppendLine_Strings Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 174.45 ns 0.875 ns 0.776 ns 174.60 ns 173.09 ns 175.42 ns 1.00 Base 0.00 0.3074 - - 1288 B 1.00
AppendLine_Strings Job-DFWZBP \8.0.0\corerun.exe ? ? ? 173.30 ns 0.644 ns 0.602 ns 173.06 ns 172.44 ns 174.45 ns 0.99 Same 0.01 0.3073 - - 1288 B 1.00
Append_Primitives Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 871.61 ns 4.007 ns 3.748 ns 871.59 ns 865.47 ns 878.13 ns 1.00 Base 0.00 0.2615 - - 1096 B 1.00
Append_Primitives Job-DFWZBP \8.0.0\corerun.exe ? ? ? 881.18 ns 6.783 ns 5.664 ns 879.61 ns 873.53 ns 889.82 ns 1.01 Same 0.01 0.2621 - - 1096 B 1.00
Append_ValueTypes Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 1,470.36 ns 15.379 ns 13.633 ns 1,468.94 ns 1,454.33 ns 1,505.18 ns 1.00 Base 0.00 0.7488 - - 3152 B 1.00
Append_ValueTypes Job-DFWZBP \8.0.0\corerun.exe ? ? ? 1,843.18 ns 7.217 ns 6.398 ns 1,843.02 ns 1,826.14 ns 1,854.63 ns 1.25 Slower 0.01 0.5756 - - 2408 B 0.76
Append_ValueTypes_Interpolated Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 1,633.49 ns 11.670 ns 10.345 ns 1,636.03 ns 1,621.15 ns 1,655.19 ns 1.00 Base 0.00 0.3767 - - 1584 B 1.00
Append_ValueTypes_Interpolated Job-DFWZBP \8.0.0\corerun.exe ? ? ? 1,760.95 ns 73.500 ns 84.643 ns 1,748.46 ns 1,645.36 ns 1,965.16 ns 1.10 Same 0.05 0.3776 - - 1584 B 1.00
Append_Memory Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 419.87 ns 2.690 ns 2.384 ns 419.76 ns 416.05 ns 425.41 ns 1.00 Base 0.00 0.8681 - - 3632 B 1.00
Append_Memory Job-DFWZBP \8.0.0\corerun.exe ? ? ? 414.48 ns 2.117 ns 1.981 ns 414.63 ns 411.10 ns 417.69 ns 0.99 Same 0.00 0.8674 - - 3632 B 1.00
Append_NonEmptySpan Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 383.87 ns 2.111 ns 1.871 ns 383.80 ns 380.91 ns 387.43 ns 1.00 Base 0.00 0.8676 - - 3632 B 1.00
Append_NonEmptySpan Job-DFWZBP \8.0.0\corerun.exe ? ? ? 380.07 ns 2.956 ns 2.469 ns 380.36 ns 377.30 ns 385.45 ns 0.99 Same 0.01 0.8677 - - 3632 B 1.00
Insert_Primitives Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 27,965.09 ns 128.254 ns 119.968 ns 27,938.07 ns 27,837.08 ns 28,234.74 ns 1.00 Base 0.00 2.4510 - - 10416 B 1.00
Insert_Primitives Job-DFWZBP \8.0.0\corerun.exe ? ? ? 28,213.10 ns 84.678 ns 79.208 ns 28,234.20 ns 28,112.17 ns 28,328.15 ns 1.01 Same 0.00 2.4686 - - 10416 B 1.00
Insert_Strings Job-MABLYB \8.0.0-ref\corerun.exe ? ? ? 487.55 ns 2.621 ns 2.452 ns 487.78 ns 484.04 ns 491.64 ns 1.00 Base 0.00 0.3170 - - 1328 B 1.00
Insert_Strings Job-DFWZBP \8.0.0\corerun.exe ? ? ? 509.14 ns 5.319 ns 4.975 ns 508.02 ns 501.35 ns 519.57 ns 1.04 Same 0.01 0.3173 - - 1328 B 1.00
Append_Strings Job-MABLYB \8.0.0-ref\corerun.exe 1 ? ? 158.31 ns 0.792 ns 0.741 ns 158.21 ns 156.94 ns 159.44 ns 1.00 Base 0.00 0.2984 - - 1248 B 1.00
Append_Strings Job-DFWZBP \8.0.0\corerun.exe 1 ? ? 159.40 ns 0.920 ns 0.861 ns 159.20 ns 158.37 ns 161.07 ns 1.01 Same 0.01 0.2981 - - 1248 B 1.00
ctor_string Job-MABLYB \8.0.0-ref\corerun.exe ? 100 ? 29.40 ns 0.169 ns 0.150 ns 29.35 ns 29.13 ns 29.67 ns 1.00 Base 0.00 0.0649 - - 272 B 1.00
ctor_string Job-DFWZBP \8.0.0\corerun.exe ? 100 ? 29.37 ns 0.141 ns 0.125 ns 29.36 ns 29.17 ns 29.61 ns 1.00 Same 0.00 0.0649 - - 272 B 1.00
ctor_capacity Job-MABLYB \8.0.0-ref\corerun.exe ? 100 ? 21.80 ns 0.104 ns 0.097 ns 21.83 ns 21.59 ns 21.93 ns 1.00 Base 0.00 0.0650 - - 272 B 1.00
ctor_capacity Job-DFWZBP \8.0.0\corerun.exe ? 100 ? 21.63 ns 0.089 ns 0.079 ns 21.63 ns 21.48 ns 21.77 ns 0.99 Same 0.01 0.0650 - - 272 B 1.00
ToString_SingleSegment Job-MABLYB \8.0.0-ref\corerun.exe ? 100 ? 21.12 ns 0.103 ns 0.091 ns 21.12 ns 21.00 ns 21.25 ns 1.00 Base 0.00 0.0535 - - 224 B 1.00
ToString_SingleSegment Job-DFWZBP \8.0.0\corerun.exe ? 100 ? 21.27 ns 0.190 ns 0.168 ns 21.28 ns 21.04 ns 21.58 ns 1.01 Same 0.01 0.0535 - - 224 B 1.00
ToString_MultipleSegments Job-MABLYB \8.0.0-ref\corerun.exe ? 100 ? 27.90 ns 0.114 ns 0.107 ns 27.89 ns 27.74 ns 28.06 ns 1.00 Base 0.00 0.0535 - - 224 B 1.00
ToString_MultipleSegments Job-DFWZBP \8.0.0\corerun.exe ? 100 ? 27.99 ns 0.105 ns 0.088 ns 27.98 ns 27.86 ns 28.18 ns 1.00 Same 0.00 0.0535 - - 224 B 1.00
Append_Char Job-MABLYB \8.0.0-ref\corerun.exe ? 100 ? 241.87 ns 1.071 ns 1.002 ns 241.60 ns 240.66 ns 243.76 ns 1.00 Base 0.00 0.1300 - - 544 B 1.00
Append_Char Job-DFWZBP \8.0.0\corerun.exe ? 100 ? 241.30 ns 0.745 ns 0.661 ns 241.33 ns 240.19 ns 242.08 ns 1.00 Same 0.01 0.1293 - - 544 B 1.00
Append_Char_Capacity Job-MABLYB \8.0.0-ref\corerun.exe ? 100 ? 184.25 ns 0.671 ns 0.628 ns 184.39 ns 183.50 ns 185.80 ns 1.00 Base 0.00 0.0646 - - 272 B 1.00
Append_Char_Capacity Job-DFWZBP \8.0.0\corerun.exe ? 100 ? 184.05 ns 0.712 ns 0.632 ns 183.91 ns 183.25 ns 185.08 ns 1.00 Same 0.00 0.0644 - - 272 B 1.00
Append_Strings Job-MABLYB \8.0.0-ref\corerun.exe 1000 ? ? 64,634.32 ns 412.374 ns 385.735 ns 64,675.87 ns 64,033.32 ns 65,094.01 ns 1.00 Base 0.00 99.1291 68.3914 - 559264 B 1.00
Append_Strings Job-DFWZBP \8.0.0\corerun.exe 1000 ? ? 64,019.56 ns 593.960 ns 555.590 ns 63,872.80 ns 63,311.83 ns 65,350.13 ns 0.99 Same 0.01 99.1736 68.1818 - 559264 B 1.00
ctor_string Job-MABLYB \8.0.0-ref\corerun.exe ? 100000 ? 86,061.34 ns 1,523.205 ns 1,350.281 ns 86,133.38 ns 83,625.24 ns 88,818.55 ns 1.00 Base 0.00 62.1622 62.1622 62.1622 200093 B 1.00
ctor_string Job-DFWZBP \8.0.0\corerun.exe ? 100000 ? 86,399.61 ns 1,641.253 ns 1,611.930 ns 86,427.58 ns 84,282.47 ns 88,997.28 ns 1.00 Same 0.02 62.1603 62.1603 62.1603 200093 B 1.00
ctor_capacity Job-MABLYB \8.0.0-ref\corerun.exe ? 100000 ? 9,895.95 ns 117.554 ns 109.960 ns 9,868.61 ns 9,753.29 ns 10,113.17 ns 1.00 Base 0.00 62.4625 62.4625 62.4625 200093 B 1.00
ctor_capacity Job-DFWZBP \8.0.0\corerun.exe ? 100000 ? 9,870.02 ns 104.713 ns 97.948 ns 9,856.19 ns 9,737.11 ns 10,055.96 ns 1.00 Same 0.01 62.4622 62.4622 62.4622 200093 B 1.00
ToString_SingleSegment Job-MABLYB \8.0.0-ref\corerun.exe ? 100000 ? 82,538.59 ns 1,611.019 ns 1,654.397 ns 82,354.08 ns 80,563.27 ns 85,254.72 ns 1.00 Base 0.00 62.1811 62.1811 62.1811 200045 B 1.00
ToString_SingleSegment Job-DFWZBP \8.0.0\corerun.exe ? 100000 ? 82,821.13 ns 1,593.155 ns 1,490.238 ns 82,308.53 ns 80,968.39 ns 85,776.82 ns 1.00 Same 0.03 62.1745 62.1745 62.1745 200045 B 1.00
ToString_MultipleSegments Job-MABLYB \8.0.0-ref\corerun.exe ? 100000 ? 88,075.89 ns 1,740.701 ns 1,709.601 ns 87,618.42 ns 85,793.44 ns 91,109.96 ns 1.00 Base 0.00 62.1566 62.1566 62.1566 200045 B 1.00
ToString_MultipleSegments Job-DFWZBP \8.0.0\corerun.exe ? 100000 ? 88,520.24 ns 1,713.739 ns 1,683.121 ns 88,292.83 ns 86,143.28 ns 90,643.43 ns 1.01 Same 0.02 62.1408 62.1408 62.1408 200045 B 1.00
Append_Char Job-MABLYB \8.0.0-ref\corerun.exe ? 100000 ? 187,315.46 ns 827.968 ns 774.482 ns 187,259.00 ns 186,417.26 ns 189,021.21 ns 1.00 Base 0.00 49.8512 1.4881 - 209968 B 1.00
Append_Char Job-DFWZBP \8.0.0\corerun.exe ? 100000 ? 187,439.21 ns 714.764 ns 668.591 ns 187,544.72 ns 186,596.65 ns 188,679.54 ns 1.00 Same 0.01 49.8512 1.4881 - 209968 B 1.00
Append_Char_Capacity Job-MABLYB \8.0.0-ref\corerun.exe ? 100000 ? 252,053.05 ns 1,392.473 ns 1,302.520 ns 252,007.74 ns 250,227.38 ns 254,778.57 ns 1.00 Base 0.00 61.5079 61.5079 61.5079 200093 B 1.00
Append_Char_Capacity Job-DFWZBP \8.0.0\corerun.exe ? 100000 ? 252,284.08 ns 880.869 ns 735.566 ns 252,625.64 ns 251,154.02 ns 253,210.07 ns 1.00 Same 0.00 61.5079 61.5079 61.5079 200093 B 1.00
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? 13.02 ns 0.079 ns 0.074 ns 13.02 ns 12.93 ns 13.15 ns 1.00 Base 0.00 0.0248 - - 104 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? 52.45 ns 0.280 ns 0.262 ns 52.46 ns 52.08 ns 53.02 ns 4.03 Slower 0.03 0.0248 - - 104 B 1.00
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? Char : A 111.69 ns 0.394 ns 0.349 ns 111.61 ns 111.27 ns 112.52 ns 1.00 Base 0.00 0.1163 - - 488 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? Char : A 123.70 ns 0.430 ns 0.402 ns 123.75 ns 123.14 ns 124.30 ns 1.11 Slower 0.00 0.0244 - - 104 B 0.21
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? Int32 : 42 103.71 ns 0.520 ns 0.461 ns 103.49 ns 103.19 ns 104.67 ns 1.00 Base 0.00 0.0494 - - 208 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? Int32 : 42 181.31 ns 0.821 ns 0.768 ns 181.44 ns 180.12 ns 182.55 ns 1.75 Slower 0.01 0.0493 - - 208 B 1.00
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? Rune : A 119.31 ns 0.538 ns 0.504 ns 119.19 ns 118.73 ns 120.41 ns 1.00 Base 0.00 0.1165 - - 488 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? Rune : A 117.21 ns 0.334 ns 0.279 ns 117.32 ns 116.80 ns 117.59 ns 0.98 Same 0.00 0.0247 - - 104 B 0.21
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? String : 40.11 ns 0.238 ns 0.223 ns 40.06 ns 39.84 ns 40.56 ns 1.00 Base 0.00 0.0247 - - 104 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? String : 95.74 ns 0.580 ns 0.543 ns 95.74 ns 95.12 ns 97.02 ns 2.39 Slower 0.02 0.0246 - - 104 B 1.00
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? String : 1234567890 186.33 ns 1.191 ns 1.056 ns 185.84 ns 185.39 ns 188.99 ns 1.00 Base 0.00 0.2081 - - 872 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? String : 1234567890 239.21 ns 1.255 ns 1.174 ns 239.16 ns 237.66 ns 241.99 ns 1.28 Slower 0.01 0.2078 - - 872 B 1.00
Append_Object Job-MABLYB \8.0.0-ref\corerun.exe ? ? Version : 1.2.3.4 652.52 ns 4.961 ns 4.641 ns 653.31 ns 644.76 ns 659.71 ns 1.00 Base 0.00 0.2812 - - 1184 B 1.00
Append_Object Job-DFWZBP \8.0.0\corerun.exe ? ? Version : 1.2.3.4 617.83 ns 7.158 ns 6.696 ns 619.80 ns 602.53 ns 624.03 ns 0.95 Same 0.01 0.1569 - - 664 B 0.56

@ghost
Copy link

ghost commented Oct 30, 2023

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

Issue Details

Closes #92193

Applying the optimization for ISpanFormattable, already implemented in AppendFormat, to Append.

Author: manandre
Assignees: -
Labels:

area-System.Runtime, community-contribution, needs-area-label

Milestone: -

@adamsitnik
Copy link
Member

It seems it won't be easy as dotnet/performance still does not support .NET 9.

I've created dotnet/performance#3453 to track progress on that.

@adamsitnik adamsitnik removed the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 30, 2023
if (value.TryFormat(RemainingCurrentChunk, out int charsWritten, format: default, provider: null))
{
if ((uint)charsWritten > (uint)RemainingCurrentChunkLength)
Copy link
Member

@stephentoub stephentoub Oct 31, 2023

Choose a reason for hiding this comment

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

Does this measurably slow down the existing uses in overloads like Append(int)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No noticeable impact measured.
See the new benchmark for more details

@stephentoub
Copy link
Member

Append_Object | Int32 : 42 | 1.75 | Slower | 1.00 | 1.75

Taking this benchmark as an example, the benchmark data shows this increasing from something like 100ns to 180ns. Have you investigated what accounts for that difference?

@jozkee jozkee added the needs-author-action An issue or pull request that requires more info or actions from the author. label Oct 31, 2023
@manandre
Copy link
Contributor Author

New attempt with some small optimizations:

Method data Ratio MannWhitney(5%) Alloc Ratio Total Ratio
Append_ValueTypes 1.28 Slower 0.76 0.95
Append_Object null 2.60 Slower 1.00 2.60
Append_Object Char : A 1.10 Slower 0.21 0.23
Append_Object Int32 : 42 1.60 Slower 1.00 1.60
Append_Object Rune : A 1.02 Same 0.21 0.21
Append_Object String : 2.39 Slower 1.00 2.39
Append_Object String : 1234567890 1.36 Slower 1.00 1.36
Append_Object Version : 1.2.3.4 0.92 Faster 0.56 0.52

Optimizations:

Benchmark summary

BenchmarkDotNet v0.13.10-nightly.20231019.90, Windows 10 (10.0.19045.3570/22H2/2022Update) Intel Core i7-4770K CPU 3.50GHz (Haswell), 1 CPU, 8 logical and 4 physical cores .NET SDK 8.0.100-rc.1.23463.5 [Host] : .NET 8.0.0 (8.0.23.41904), X64 RyuJIT AVX2 Job-TZIULB : .NET 8.0.0 (42.42.42.42424), X64 RyuJIT AVX2 Job-HAQEJN : .NET 8.0.0 (42.42.42.42424), X64 RyuJIT AVX2

PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true IterationTime=250.0000 ms
MaxIterationCount=20 MinIterationCount=15 WarmupCount=1

Method Job Toolchain repeat length data Mean Error StdDev Median Min Max Ratio MannWhitney(5%) RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
AppendLine_Strings Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 176.32 ns 1.786 ns 1.670 ns 175.90 ns 174.06 ns 179.30 ns 1.00 Base 0.00 0.3079 - - 1288 B 1.00
AppendLine_Strings Job-HAQEJN \8.0.0\corerun.exe ? ? ? 175.00 ns 0.953 ns 0.845 ns 174.75 ns 173.98 ns 177.01 ns 0.99 Same 0.01 0.3078 - - 1288 B 1.00
Append_Primitives Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 869.10 ns 8.138 ns 7.214 ns 867.57 ns 859.08 ns 887.11 ns 1.00 Base 0.00 0.2590 - - 1096 B 1.00
Append_Primitives Job-HAQEJN \8.0.0\corerun.exe ? ? ? 895.44 ns 16.440 ns 14.574 ns 889.78 ns 882.77 ns 924.08 ns 1.03 Same 0.02 0.2596 - - 1096 B 1.00
Append_ValueTypes Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 1,476.86 ns 14.795 ns 13.115 ns 1,472.30 ns 1,463.17 ns 1,504.70 ns 1.00 Base 0.00 0.7492 - - 3152 B 1.00
Append_ValueTypes Job-HAQEJN \8.0.0\corerun.exe ? ? ? 1,897.06 ns 14.000 ns 12.411 ns 1,891.83 ns 1,881.45 ns 1,920.08 ns 1.28 Slower 0.01 0.5714 - - 2408 B 0.76
Append_ValueTypes_Interpolated Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 1,788.26 ns 11.765 ns 9.824 ns 1,789.73 ns 1,777.33 ns 1,809.23 ns 1.00 Base 0.00 0.3753 - - 1584 B 1.00
Append_ValueTypes_Interpolated Job-HAQEJN \8.0.0\corerun.exe ? ? ? 1,697.81 ns 7.860 ns 6.968 ns 1,696.03 ns 1,687.88 ns 1,712.35 ns 0.95 Same 0.01 0.3734 - - 1584 B 1.00
Append_Memory Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 434.02 ns 10.320 ns 11.884 ns 431.15 ns 418.58 ns 451.38 ns 1.00 Base 0.00 0.8674 - - 3632 B 1.00
Append_Memory Job-HAQEJN \8.0.0\corerun.exe ? ? ? 418.25 ns 2.382 ns 1.989 ns 418.24 ns 415.62 ns 421.44 ns 0.95 Same 0.02 0.8677 - - 3632 B 1.00
Append_NonEmptySpan Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 377.96 ns 1.308 ns 1.160 ns 377.92 ns 376.06 ns 379.79 ns 1.00 Base 0.00 0.8673 - - 3632 B 1.00
Append_NonEmptySpan Job-HAQEJN \8.0.0\corerun.exe ? ? ? 377.13 ns 3.054 ns 2.384 ns 376.36 ns 374.32 ns 381.85 ns 1.00 Same 0.01 0.8669 - - 3632 B 1.00
Insert_Primitives Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 28,334.26 ns 179.942 ns 159.514 ns 28,357.55 ns 28,135.34 ns 28,725.80 ns 1.00 Base 0.00 2.3777 - - 10416 B 1.00
Insert_Primitives Job-HAQEJN \8.0.0\corerun.exe ? ? ? 28,224.64 ns 221.316 ns 184.809 ns 28,116.62 ns 28,022.52 ns 28,524.80 ns 1.00 Same 0.01 2.4819 - - 10416 B 1.00
Insert_Strings Job-TZIULB \8.0.0-ref\corerun.exe ? ? ? 487.76 ns 2.704 ns 2.529 ns 487.14 ns 483.83 ns 491.90 ns 1.00 Base 0.00 0.3162 - - 1328 B 1.00
Insert_Strings Job-HAQEJN \8.0.0\corerun.exe ? ? ? 492.49 ns 9.625 ns 9.003 ns 488.76 ns 482.26 ns 509.80 ns 1.01 Same 0.02 0.3168 - - 1328 B 1.00
Append_Strings Job-TZIULB \8.0.0-ref\corerun.exe 1 ? ? 163.24 ns 4.070 ns 4.687 ns 163.78 ns 157.21 ns 173.39 ns 1.00 Base 0.00 0.2979 - - 1248 B 1.00
Append_Strings Job-HAQEJN \8.0.0\corerun.exe 1 ? ? 163.10 ns 3.538 ns 4.074 ns 164.47 ns 157.34 ns 168.85 ns 1.00 Same 0.04 0.2981 - - 1248 B 1.00
ctor_string Job-TZIULB \8.0.0-ref\corerun.exe ? 100 ? 30.51 ns 0.773 ns 0.890 ns 29.98 ns 29.62 ns 32.13 ns 1.00 Base 0.00 0.0649 - - 272 B 1.00
ctor_string Job-HAQEJN \8.0.0\corerun.exe ? 100 ? 29.71 ns 0.123 ns 0.109 ns 29.70 ns 29.54 ns 29.88 ns 0.97 Same 0.03 0.0650 - - 272 B 1.00
ctor_capacity Job-TZIULB \8.0.0-ref\corerun.exe ? 100 ? 22.74 ns 0.547 ns 0.630 ns 22.53 ns 22.07 ns 23.92 ns 1.00 Base 0.00 0.0649 - - 272 B 1.00
ctor_capacity Job-HAQEJN \8.0.0\corerun.exe ? 100 ? 22.58 ns 0.557 ns 0.641 ns 22.23 ns 21.94 ns 23.61 ns 0.99 Same 0.03 0.0650 - - 272 B 1.00
ToString_SingleSegment Job-TZIULB \8.0.0-ref\corerun.exe ? 100 ? 21.79 ns 0.486 ns 0.541 ns 21.49 ns 21.30 ns 22.72 ns 1.00 Base 0.00 0.0535 - - 224 B 1.00
ToString_SingleSegment Job-HAQEJN \8.0.0\corerun.exe ? 100 ? 22.82 ns 0.062 ns 0.048 ns 22.83 ns 22.74 ns 22.88 ns 1.04 Same 0.03 0.0535 - - 224 B 1.00
ToString_MultipleSegments Job-TZIULB \8.0.0-ref\corerun.exe ? 100 ? 29.06 ns 0.689 ns 0.794 ns 29.52 ns 27.87 ns 30.32 ns 1.00 Base 0.00 0.0535 - - 224 B 1.00
ToString_MultipleSegments Job-HAQEJN \8.0.0\corerun.exe ? 100 ? 28.07 ns 0.455 ns 0.403 ns 27.89 ns 27.70 ns 28.96 ns 0.97 Same 0.03 0.0535 - - 224 B 1.00
Append_Char Job-TZIULB \8.0.0-ref\corerun.exe ? 100 ? 251.72 ns 5.350 ns 6.161 ns 252.65 ns 243.47 ns 261.01 ns 1.00 Base 0.00 0.1293 - - 544 B 1.00
Append_Char Job-HAQEJN \8.0.0\corerun.exe ? 100 ? 256.72 ns 2.482 ns 2.321 ns 256.01 ns 253.76 ns 260.98 ns 1.02 Same 0.03 0.1298 - - 544 B 1.00
Append_Char_Capacity Job-TZIULB \8.0.0-ref\corerun.exe ? 100 ? 185.03 ns 0.835 ns 0.740 ns 184.83 ns 184.23 ns 186.83 ns 1.00 Base 0.00 0.0649 - - 272 B 1.00
Append_Char_Capacity Job-HAQEJN \8.0.0\corerun.exe ? 100 ? 197.20 ns 3.480 ns 3.255 ns 197.89 ns 188.80 ns 200.87 ns 1.06 Slower 0.02 0.0644 - - 272 B 1.00
Append_Strings Job-TZIULB \8.0.0-ref\corerun.exe 1000 ? ? 65,992.37 ns 2,104.099 ns 2,423.083 ns 64,983.75 ns 63,287.88 ns 69,519.35 ns 1.00 Base 0.00 99.1848 68.4783 - 559264 B 1.00
Append_Strings Job-HAQEJN \8.0.0\corerun.exe 1000 ? ? 67,441.30 ns 2,219.309 ns 2,555.760 ns 68,829.80 ns 63,651.26 ns 70,694.17 ns 1.02 Same 0.03 99.0513 68.3594 - 559264 B 1.00
ctor_string Job-TZIULB \8.0.0-ref\corerun.exe ? 100000 ? 76,852.49 ns 1,689.878 ns 1,946.066 ns 76,834.70 ns 72,602.80 ns 79,405.60 ns 1.00 Base 0.00 62.2024 62.2024 62.2024 200093 B 1.00
ctor_string Job-HAQEJN \8.0.0\corerun.exe ? 100000 ? 77,613.96 ns 1,387.987 ns 1,298.324 ns 77,758.55 ns 75,423.53 ns 79,770.65 ns 1.02 Same 0.03 62.1936 62.1936 62.1936 200093 B 1.00
ctor_capacity Job-TZIULB \8.0.0-ref\corerun.exe ? 100000 ? 9,234.93 ns 97.684 ns 86.594 ns 9,226.39 ns 9,058.55 ns 9,372.33 ns 1.00 Base 0.00 62.4658 62.4658 62.4658 200093 B 1.00
ctor_capacity Job-HAQEJN \8.0.0\corerun.exe ? 100000 ? 9,259.14 ns 69.742 ns 65.236 ns 9,242.88 ns 9,164.18 ns 9,382.13 ns 1.00 Same 0.01 62.4652 62.4652 62.4652 200093 B 1.00
ToString_SingleSegment Job-TZIULB \8.0.0-ref\corerun.exe ? 100000 ? 74,861.36 ns 1,075.510 ns 1,006.033 ns 74,664.30 ns 73,128.06 ns 76,262.59 ns 1.00 Base 0.00 62.1995 62.1995 62.1995 200045 B 1.00
ToString_SingleSegment Job-HAQEJN \8.0.0\corerun.exe ? 100000 ? 74,472.80 ns 1,001.324 ns 887.648 ns 74,564.35 ns 72,897.67 ns 75,792.43 ns 0.99 Same 0.02 62.2010 62.2010 62.2010 200045 B 1.00
ToString_MultipleSegments Job-TZIULB \8.0.0-ref\corerun.exe ? 100000 ? 79,509.55 ns 1,643.880 ns 1,893.094 ns 79,631.81 ns 74,305.85 ns 82,563.99 ns 1.00 Base 0.00 62.1891 62.1891 62.1891 200045 B 1.00
ToString_MultipleSegments Job-HAQEJN \8.0.0\corerun.exe ? 100000 ? 80,059.91 ns 1,497.933 ns 1,471.170 ns 80,684.54 ns 75,999.73 ns 81,571.68 ns 1.01 Same 0.02 62.1981 62.1981 62.1981 200045 B 1.00
Append_Char Job-TZIULB \8.0.0-ref\corerun.exe ? 100000 ? 194,260.88 ns 4,245.082 ns 4,888.643 ns 194,229.55 ns 187,859.89 ns 202,332.52 ns 1.00 Base 0.00 49.8418 0.7911 - 209968 B 1.00
Append_Char Job-HAQEJN \8.0.0\corerun.exe ? 100000 ? 198,968.54 ns 1,218.231 ns 1,139.534 ns 198,867.64 ns 197,097.47 ns 201,396.99 ns 1.03 Same 0.03 49.8418 0.7911 - 209968 B 1.00
Append_Char_Capacity Job-TZIULB \8.0.0-ref\corerun.exe ? 100000 ? 247,854.18 ns 4,896.577 ns 4,809.094 ns 246,074.21 ns 242,210.52 ns 255,991.57 ns 1.00 Base 0.00 61.5079 61.5079 61.5079 200093 B 1.00
Append_Char_Capacity Job-HAQEJN \8.0.0\corerun.exe ? 100000 ? 254,185.26 ns 2,652.187 ns 2,480.857 ns 253,832.16 ns 250,264.52 ns 257,747.58 ns 1.03 Same 0.02 61.4919 61.4919 61.4919 200093 B 1.00
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? 14.14 ns 0.193 ns 0.181 ns 14.13 ns 13.81 ns 14.36 ns 1.00 Base 0.00 0.0248 - - 104 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? 36.82 ns 0.750 ns 0.701 ns 37.09 ns 35.09 ns 37.84 ns 2.60 Slower 0.06 0.0248 - - 104 B 1.00
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? Char : A 117.94 ns 1.015 ns 0.950 ns 117.97 ns 116.68 ns 119.68 ns 1.00 Base 0.00 0.1165 - - 488 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? Char : A 129.13 ns 2.654 ns 2.950 ns 130.53 ns 123.53 ns 131.60 ns 1.10 Slower 0.03 0.0244 - - 104 B 0.21
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? Int32 : 42 119.83 ns 2.857 ns 3.057 ns 120.92 ns 111.77 ns 123.06 ns 1.00 Base 0.00 0.0495 - - 208 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? Int32 : 42 191.72 ns 3.678 ns 3.612 ns 192.06 ns 183.00 ns 197.07 ns 1.60 Slower 0.05 0.0495 - - 208 B 1.00
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? Rune : A 126.17 ns 1.160 ns 1.085 ns 126.18 ns 123.81 ns 127.96 ns 1.00 Base 0.00 0.1163 - - 488 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? Rune : A 128.93 ns 1.614 ns 1.510 ns 128.26 ns 127.14 ns 132.24 ns 1.02 Same 0.01 0.0247 - - 104 B 0.21
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? String : 41.19 ns 1.011 ns 1.164 ns 41.03 ns 39.78 ns 43.90 ns 1.00 Base 0.00 0.0248 - - 104 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? String : 98.90 ns 0.990 ns 0.827 ns 98.66 ns 97.94 ns 100.63 ns 2.39 Slower 0.07 0.0248 - - 104 B 1.00
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? String : 1234567890 189.93 ns 1.681 ns 1.404 ns 189.91 ns 188.33 ns 193.26 ns 1.00 Base 0.00 0.2082 - - 872 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? String : 1234567890 256.81 ns 5.046 ns 4.955 ns 258.63 ns 246.42 ns 263.39 ns 1.36 Slower 0.03 0.2084 - - 872 B 1.00
Append_Object Job-TZIULB \8.0.0-ref\corerun.exe ? ? Version : 1.2.3.4 693.07 ns 9.055 ns 8.470 ns 691.90 ns 677.27 ns 707.73 ns 1.00 Base 0.00 0.2820 - - 1184 B 1.00
Append_Object Job-HAQEJN \8.0.0\corerun.exe ? ? Version : 1.2.3.4 634.11 ns 4.454 ns 4.167 ns 633.98 ns 628.55 ns 642.33 ns 0.92 Faster 0.01 0.1587 - - 664 B 0.56

@ghost ghost removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Nov 11, 2023
@manandre
Copy link
Contributor Author

Append_Object | Int32 : 42 | 1.75 | Slower | 1.00 | 1.75

Taking this benchmark as an example, the benchmark data shows this increasing from something like 100ns to 180ns. Have you investigated what accounts for that difference?

I have investigated this particular use case (Append_Object with an Int32 value) using the ETW profiler and Perfview.
Reference:
image

New:
image

I was expecting the impact of the cast attempt to ISpanFormattable (via the IsInstanceOfInterface method), but I do not understand why:

  • Reference: execution time in the tested method Append(class System.object) is 0.1% of the total !?!
  • New: some execution time is spent in the Append(class System.String) method whereas it should not be called at all!?!

Can someone please help me here to interpret these results?

@stephentoub
Copy link
Member

New: some execution time is spent in the Append(class System.String) method whereas it should not be called at all!?!

Why wouldn't it be called at all? The runtime itself uses StringBuilder. Are these calls not coming from its use?

Reference: execution time in the tested method Append(class System.object) is 0.1% of the total !?!

Can you elaborate? Are you confused about it being smaller than expected or larger than expected?

@manandre
Copy link
Contributor Author

manandre commented Feb 4, 2024

I have found the explanation for the Int32: 42 case. There is a lazy-populated cache for numbers below 300, which is only used in the ToString() path and not in the TryFormat() one. It now clearly explains the surprising allocation ratio of 1.0...

@manandre
Copy link
Contributor Author

New benchmark execution after rebase on main branch and with .NET 9 preview 1:

Method data Ratio MannWhitney(5%) Alloc Ratio Total Ratio
Append_ValueTypes 1.24 Slower 0.76 0.94
Append_Object null 2.57 Slower 1.00 2.57
Append_Object Char : A 0.79 Faster 0.21 0.17
Append_Object Int32 : 4242 0.85 Faster 1.00 0.85
Append_Object Rune : A 0.86 Faster 0.21 0.18
Append_Object String : 1.24 Slower 1.00 1.24
Append_Object String : 1234567890 1.05 Slower 1.00 1.05
Append_Object Version : 1.2.3.4 0.87 Faster 0.56 0.49

@stephentoub
Copy link
Member

I've spent some time browsing around use of Append(object), and the majority of places I see this being used wouldn't benefit from an ISpanFormattable check; the actual type often ends up being strings or other such instances. Given that there is a regression for such use, until we have better data that suggests this will be a net win, I think we should close this. Thanks for your work on it, @manandre.

@stephentoub stephentoub closed this Jul 1, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Aug 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
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.

Add support for ISpanFormattable in StringBuilder.Append(Object)
7 participants