Skip to content

Use ValueStringBuilder in HttpLoggingMiddleware #33050

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 4 commits into from
Jul 1, 2021

Conversation

Kahbazi
Copy link
Member

@Kahbazi Kahbazi commented May 26, 2021

No description provided.

@ghost ghost added area-runtime community-contribution Indicates that the PR has been added by a community member labels May 26, 2021
Copy link
Member

@BrennanConroy BrennanConroy left a comment

Choose a reason for hiding this comment

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

Can you share perf numbers for this please

@davidfowl
Copy link
Member

I would use ValueStringBuilder instead

@BrennanConroy
Copy link
Member

I would use ValueStringBuilder instead

You keep mentioning that, but it's internal to Runtime 😆

@davidfowl
Copy link
Member

Copy it and make it internal in ASP.NET Core.

@BrennanConroy BrennanConroy self-assigned this May 26, 2021
@BrennanConroy
Copy link
Member

ValueStringBuilder will be available in the repo once #32600 is merged

@Kahbazi Kahbazi force-pushed the kahbazi/HttpLoggingStringCreate branch from bca79dc to 7dca017 Compare May 28, 2021 14:16
@davidfowl
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@Kahbazi Kahbazi changed the title Use string.Create in HttpLoggingMiddleware Use ValueStringBuilder in HttpLoggingMiddleware May 29, 2021
@@ -37,8 +37,7 @@ public override string ToString()
{
if (_cachedToString == null)
{
// TODO use string.Create instead of a StringBuilder here.
var builder = new StringBuilder();
var builder = new ValueStringBuilder();
Copy link
Member

Choose a reason for hiding this comment

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

Can we come up with a rough guess of how big the string will be? Otherwise this will need to do a lot of internal copying and renting whenever it grows. For example, each _keyValues will have at minimum ~5 characters output + 8 for "Request:\n".

I'd love to see perf numbers from before and after, and with a guess at the size here so we can see that improvements are actually being made.

Copy link
Member Author

Choose a reason for hiding this comment

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

What should I use to get the perf numbers? BenchmarkDotNet?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, take a look at https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/perf/Microbenchmarks/HandshakeBenchmark.cs for an example in the middleware area. You go to that folder and run dotnet run -c Release then select which benchmark to run if there are multiple to choose from.

Copy link
Member Author

Choose a reason for hiding this comment

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

Here's the result with this list

list = new List<KeyValuePair<string, string>>();
list.Add(new KeyValuePair<string, string>("Protocol", "HTTP/1.1"));
list.Add(new KeyValuePair<string, string>("Method", "GET"));
list.Add(new KeyValuePair<string, string>("Scheme", "http"));
list.Add(new KeyValuePair<string, string>("PathBase", "/"));
list.Add(new KeyValuePair<string, string>("Path", "/"));
list.Add(new KeyValuePair<string, string>("QueryString", ""));
list.Add(new KeyValuePair<string, string>("Connection", "KeepAlive"));
list.Add(new KeyValuePair<string, string>("Content-Type", "text/plain"));

and the initial capacity is 8 + _keyValues.Count * 5.

Method Mean Error StdDev Median Op/s Gen 0 Gen 1 Gen 2 Allocated
StringBuilder 681.8 ns 13.62 ns 32.38 ns 669.2 ns 1,466,662.8 0.0448 - - 1,208 B
ValueStringBuilder 674.5 ns 6.26 ns 5.22 ns 672.9 ns 1,482,670.6 0.0124 - - 336 B
StringBuilderWithCapacity 530.5 ns 5.89 ns 4.60 ns 529.6 ns 1,884,897.5 0.0343 - - 936 B
ValueStringBuilderWithCapacity 684.1 ns 4.40 ns 3.68 ns 683.1 ns 1,461,677.9 0.0124 - - 336 B

Copy link
Member

@BrennanConroy BrennanConroy Jun 3, 2021

Choose a reason for hiding this comment

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

That's a big difference between StringBuilderWithCapacity and ValueStringBuilderWithCapacity. Could you try initial capacity of 8 + _keyValues.Count * 100 for both? In the * 5 case, none of the names are going to be 1 byte, that was just the bare minimum, so a grow will happen which causes a copy and new rent.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it is good enough. I had to make the HttpRequestLog public and also add a method which get the capacity as input. Also I had both StringBuilder and ValueStringBuilder in HttpRequestLog for the benchmark.

Copy link
Member

Choose a reason for hiding this comment

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

@Tratcher Thoughts on a good default capacity? We would prefer slightly over-estimating the total size of the headers.

Copy link
Member

@Tratcher Tratcher Jun 9, 2021

Choose a reason for hiding this comment

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

All request headers on average? 4-16kb. Start at 2kb for the simple case though. Let the big requests pay the resize cost.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Tratcher What's the average on all response headers? Is 2kb good enough for response too?

Copy link
Member

Choose a reason for hiding this comment

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

That should be fine for now, there tend to be less response headers than request headers.

@BrennanConroy
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@BrennanConroy BrennanConroy merged commit 3ce6a78 into dotnet:main Jul 1, 2021
@ghost ghost added this to the 6.0-preview7 milestone Jul 1, 2021
@BrennanConroy
Copy link
Member

Thanks @Kahbazi !

@amcasey amcasey added area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares 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.

6 participants