Skip to content

Shrink StringValues #1283

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

Merged
merged 1 commit into from
Mar 27, 2019
Merged

Shrink StringValues #1283

merged 1 commit into from
Mar 27, 2019

Conversation

benaadams
Copy link
Member

@benaadams benaadams commented Mar 19, 2019

Use a single field to shrink by 8 bytes; shrinking ResponseHeaders by 272 bytes and RequestHeaders by 352 bytes; or 624 bytes total (e.g. per connection).

Before
image

After
image

                     Method |      Mean |     Error |    StdDev |          Op/s | Allocated |
 -------------------------- |----------:|----------:|----------:|--------------:|----------:|
-              ClearHeaders | 17.307 ns | 0.0695 ns | 0.0650 ns |  57,781,381.7 |       0 B |
+              ClearHeaders |  9.013 ns | 0.0243 ns | 0.0227 ns | 110,945,805.6 |       0 B |
-               Ctor_String |  3.687 ns | 0.0737 ns | 0.0932 ns | 271,251,797.5 |       0 B |
+               Ctor_String |  1.804 ns | 0.0065 ns | 0.0057 ns | 554,381,580.7 |       0 B |
-                Ctor_Array |  3.840 ns | 0.0122 ns | 0.0115 ns | 260,437,356.7 |       0 B |
+                Ctor_Array |  2.148 ns | 0.0076 ns | 0.0071 ns | 465,469,766.3 |       0 B |
- Index_FirstElement_String |  3.518 ns | 0.0184 ns | 0.0163 ns | 284,224,537.0 |       0 B |
+ Index_FirstElement_String |  2.647 ns | 0.0079 ns | 0.0074 ns | 377,742,529.8 |       0 B |
-  Index_FirstElement_Array |  4.185 ns | 0.0156 ns | 0.0146 ns | 238,951,422.2 |       0 B |
+  Index_FirstElement_Array |  2.711 ns | 0.0092 ns | 0.0077 ns | 368,895,093.2 |       0 B |
-              Count_String |  2.364 ns | 0.0075 ns | 0.0067 ns | 423,040,364.6 |       0 B |
+              Count_String |  2.439 ns | 0.0067 ns | 0.0049 ns | 410,002,357.0 |       0 B |
-               Count_Array |  2.448 ns | 0.0157 ns | 0.0139 ns | 408,524,150.6 |       0 B |
+               Count_Array |  3.067 ns | 0.0084 ns | 0.0074 ns | 326,031,846.5 |       0 B |
-            ForEach_String | 13.759 ns | 0.0395 ns | 0.0370 ns |  72,681,850.1 |       0 B |
+            ForEach_String | 13.759 ns | 0.0256 ns | 0.0227 ns |  72,678,544.0 |       0 B |
-             ForEach_Array | 23.041 ns | 0.0673 ns | 0.0597 ns |  43,401,774.7 |       0 B |
+             ForEach_Array | 21.331 ns | 0.0388 ns | 0.0303 ns |  46,879,326.7 |       0 B |
-               PassByValue |  5.475 ns | 0.0214 ns | 0.0200 ns | 182,652,278.6 |       0 B |
+               PassByValue |  4.615 ns | 0.0049 ns | 0.0036 ns | 216,697,441.6 |       0 B |

Resolves #1290

@benaadams benaadams force-pushed the shirnk-stringvalues branch from 885f326 to 6cc0698 Compare March 20, 2019 22:00
@benaadams benaadams marked this pull request as ready for review March 20, 2019 22:01
@benaadams benaadams requested a review from Tratcher as a code owner March 20, 2019 22:01
@Tratcher
Copy link
Member

@davidfowl we've tried this before. Why did we abandon it last time?

@davidfowl
Copy link
Member

@davidfowl we've tried this before. Why did we abandon it last time?

The last time we made the struct bigger by adding and encoding and a byte[] along with a string and string[] fields making the struct too big which resulting in tons of large copies.

This change is good! I still want to look at what it would take to store utf8bytes as well.

@benaadams
Copy link
Member Author

Follow up would be to drop the ins from Kestrel apis (which are all internal apis) as StringValues is now pointer sized.

@Tratcher Tratcher merged commit 2f8e1fb into dotnet:master Mar 27, 2019
@Tratcher Tratcher added this to the 3.0.0-preview4 milestone Mar 27, 2019
@benaadams benaadams deleted the shirnk-stringvalues branch March 28, 2019 00:39
@halter73
Copy link
Member

halter73 commented Apr 9, 2019

The last time we made the struct bigger by adding and encoding and a byte[] along with a string and string[] fields making the struct too big which resulting in tons of large copies.

@davidfowl @benaadams @JunTaoLuo

Back then we experimented with that, we tried changing the StringValues struct to include only a single _value object and type-checking to determine if _value was null, string, string[] or StringsAndEncodedValues. Basically what this PR does. It improved perf significantly in our benchmarks.

Then we tried naively leaving three typed fields (string _value, string[] _values and byte[] _encodedValues) and changing StringValues to a class. This yielded just-as-good-if-not-better performance than making StringValues a single-object StringValues, and also made the code a bit simpler since it checked if each field was set instead of type-checking a single _value object.

I think the reason we never made the change to make StringValues a single-object struct before is the fact that we had the "better" fix of changing StringValues to class that was too breaking ready to go.

Now that we're approaching 3.0, can we just make the breaking change and make StringValues a class? We still wouldn't need to allocate for null (since the StringValue could actually be null), and in the other cases your generally allocating strings or string arrays anyway. If you're using cached strings/string arrays, you could instead use cached StringValues.

@benaadams
Copy link
Member Author

benaadams commented Apr 10, 2019

Now that we're approaching 3.0, can we just make the breaking change and make StringValues a class? We still wouldn't need to allocate for null (since the StringValue could actually be null)

If you wanted to add Nullability annotations it would make it horrible as every callsite would have to be StringValues?; whereas with the struct you only need to annotate StringValues itself.

Also it pushes null up into every usage e.g. value.Count vs value?.Count ?? 0

@benaadams
Copy link
Member Author

Another expression of this is you can happily do

var charCount = RequestHeaders["random-header"].ToString().Length;
var elementCount = RequestHeaders["random-header"].ToArray().Count();

Whereas if it was nullable then it should be more like?

var charCount = RequestHeaders["random-header"]?.ToString()?.Length ?? 0;
var elementCount = RequestHeaders["random-header"]?.ToArray()?.Count ?? 0;

@halter73
Copy link
Member

Even without nullability annotations, it's probably too big a change to be worth trying this late in the 3.0 dev cycle.

I also cannot guarantee that a StringValues class still benchmarks better than a single-object StringValues struct, but that's how I remember things from over three years ago before 1.0 when we first looked into it, and I don't have any specific reason to suspect this has changed.

I just figured I'd bring it up the possibility, so everyone remembers making StringValues a class might be an option. Who knows, maybe the change is easier than I think it would be.

@halter73
Copy link
Member

Also, if we're making breaking changes you could make a ToArray() extension method that allows for null. You're probably SOL for ToString() though 😉

maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Feb 28, 2020
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Mar 2, 2020
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Mar 11, 2020
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Mar 27, 2020
maryamariyan pushed a commit to maryamariyan/performance that referenced this pull request Apr 3, 2020
maryamariyan pushed a commit to maryamariyan/performance that referenced this pull request Apr 17, 2020
adamsitnik added a commit to dotnet/performance that referenced this pull request Jun 23, 2020
* Reorganize source code

In preparation for merging many other projects into this repo, this establishes a new source code organization which groups projects together based on subject matter.


Commit migrated from dotnet/extensions@7ce647c

* Merge branch 'release/2.1' into release/2.2


Commit migrated from dotnet/extensions@18fcffb

* Merge branch 'release/2.2'


Commit migrated from dotnet/extensions@34204b6

* Reorganize source code in preparation to move into aspnet/Extensions

Prior to reorganization, this source code was found in https://github.com/aspnet/DependencyInjection/tree/7a283947c231b6585c8ac95e653950b660f3da96


Commit migrated from dotnet/extensions@689c4a8

* Reorganize source code in preparation to move into aspnet/Extensions

Prior to reorganization, this source code was found in https://github.com/aspnet/DependencyInjection/tree/12eef5e86e965b9611221c72c169002e6f3038ee


Commit migrated from dotnet/extensions@04e957b

* Reorganize source code in preparation to move into aspnet/Extensions

Prior to reorganization, this source code was found in https://github.com/aspnet/DependencyInjection/tree/d77b090567a1e6ad9a5bb5fd05f4bdcf281d4185


Commit migrated from dotnet/extensions@9a4c61b

* Reorganize source code in preparation to move into aspnet/Extensions

Prior to reorganization, this source code was found in https://github.com/aspnet/Logging/tree/f7d8e4e0537eaab54dcf28c2b148b82688a3d62d


Commit migrated from dotnet/extensions@f3cd14a

* Reorganize source code in preparation to move into aspnet/Extensions

Prior to reorganization, this source code was found in https://github.com/aspnet/HttpClientFactory/tree/cf7cf83ee869ed50a41852f5e880d073386b7fe7


Commit migrated from dotnet/extensions@c38f9c1

* Use the SharedSourceRoot variable in project files


Commit migrated from dotnet/extensions@888bcba

* Allow caching of IEnumerable services (dotnet/extensions#575)



Commit migrated from dotnet/extensions@c89a5b5

* Reduce event source logger allocations (dotnet/extensions#870)



Commit migrated from dotnet/extensions@091ee13

* Use Arcade (dotnet/extensions#586)

Use arcade


Commit migrated from dotnet/extensions@f045899

* Cleanup conversion to Arcade (dotnet/extensions#1014)

* Remove obsolete targets, properties, and scripts
* Replace IsProductComponent with IsShipping
* Undo bad merge to version.props
* Update documentation, and put workarounds into a common file
* Replace usages of RepositoryRoot with RepoRoot
* Remove API baselines
* Remove unnecessary restore feeds and split workarounds into two files
* Enable PR checks on all branches, and disable autocancel


Commit migrated from dotnet/extensions@f41cfde

* Shrink StringValues (dotnet/extensions#1283)



Commit migrated from dotnet/extensions@2f8e1fb

* Disable transitive project references in test projects (dotnet/extensions#1834)



Commit migrated from dotnet/extensions@e7d5bea

* Fix HTTP client benchmarks


Commit migrated from dotnet/extensions@8f0f7fa

* Add typed client creation benchmark


Commit migrated from dotnet/extensions@20ce03a

* Support netcoreapp3.1 TFM (dotnet/extensions#2336)

* Support netcoreapp3.1 TFM

* Unpin SDK for source build

* Update to preview1 branding


Commit migrated from dotnet/extensions@32cc816

* Normalize all file headers to the expected Apache 2.0 license


Commit migrated from dotnet/extensions@cec6e75

* Switch file headers to the MIT license


Commit migrated from dotnet/extensions@321a30c

* Moves Microsoft.Extensions.* perf tests over

* Using the Open key to fully sign assembly

- Needed to later get InternalsVisibleTo on DI

* Apply suggestions from code review

Make batch commit using suggestions in PR feedback

Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>

* Fix compile + other feedback

* Apply suggestions from code review

Co-authored-by: Nate McMaster <nate.mcmaster@microsoft.com>
Co-authored-by: Pavel Krymets <pavel@krymets.com>
Co-authored-by: Ryan Brandenburg <rybrande@microsoft.com>
Co-authored-by: Nate McMaster <natemcmaster@users.noreply.github.com>
Co-authored-by: Ben Adams <thundercat@illyriad.co.uk>
Co-authored-by: Ryan Nowak <rynowak@microsoft.com>
Co-authored-by: John Luo <johluo@microsoft.com>
Co-authored-by: Sam Harwell <Sam.Harwell@microsoft.com>
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@ghost ghost locked as resolved and limited conversation to collaborators May 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants