HeaderUtilities enhancements#738
Conversation
| return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result); | ||
| } | ||
|
|
||
| internal static unsafe bool TryParseInt32(StringSegment value, out int result) |
There was a problem hiding this comment.
Not sure if we should use the same name as the older slower TryParseInt32/64 since there are a few behaviour differences. We parse only positive numbers for example, and we don't check for overflows. Also, it's hard to know without looking at the source that the two versions have different performance.
There was a problem hiding this comment.
At least add doc comments for the behavior expectations.
There was a problem hiding this comment.
Does the old one need to parse negatives? I'd be surprised. You can probably have the old one call the new one.
Add some overflow checks.
There was a problem hiding this comment.
Why is this internal and TryParseInt64 public?
Even tough this is in the HeaderUtilites class, I think that it should probably be renamed. If not, the doc comments should clearly explain why it's different from int.TryParse.
There was a problem hiding this comment.
Yea not sure why the old ParseInt32 is internal but I kept the same encapsulation. Should we make that public too?
There was a problem hiding this comment.
Ok then, so TryParseInt64 is public because you're using it from Kestrel then? There should be doc comments on the public one explaining the limitations.
| private static bool TryParseInt64FromHeaderValue(int startIndex, string headerValue, out long value) | ||
| { | ||
| var found = false; | ||
| while (startIndex != headerValue.Length) |
There was a problem hiding this comment.
Could potentially turn this into unsafe code too. Not sure if that makes it any faster.
| private const string PrivateString = "private"; | ||
| private const string ProxyRevalidateString = "proxy-revalidate"; | ||
| private const string PublicString = "public"; | ||
| private const string SharedMaxAgeString = "s-maxage"; |
There was a problem hiding this comment.
Have we learned nothing from #712!? I actually prefer const as you can see if you read the PR comments, but we should be consistent and use static readonly instead.
There was a problem hiding this comment.
Apparently we were using these values in a switch statement and they need to be const for that.
There was a problem hiding this comment.
I don't know if David will be OK with this because of the switch statement. Like I said, I'm fine with consts given we don't mess up the values, but I would like consistency.
There was a problem hiding this comment.
Modified the switch statement. These strings are now public static readonly.
There was a problem hiding this comment.
👎 This regressed the code readability and maintainability. These constants aren't worth it.
There was a problem hiding this comment.
Also avoiding a ToLower() which is required since switch on strings are case-sensitive. I'm seeing a one percent performance increase in ResponseCaching with it. Also it's code within a private method, so I'm not too concerned about its readability.
| private const string ProxyRevalidateString = "proxy-revalidate"; | ||
| private const string PublicString = "public"; | ||
| private const string SharedMaxAgeString = "s-maxage"; | ||
| public const string MaxAgeString = "max-age"; |
| return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result); | ||
| } | ||
|
|
||
| internal static unsafe bool TryParseInt32(StringSegment value, out int result) |
There was a problem hiding this comment.
At least add doc comments for the behavior expectations.
| return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result); | ||
| } | ||
|
|
||
| internal static unsafe bool TryParseInt32(StringSegment value, out int result) |
There was a problem hiding this comment.
Does the old one need to parse negatives? I'd be surprised. You can probably have the old one call the new one.
Add some overflow checks.
| } | ||
| } | ||
|
|
||
| public static unsafe bool TryParseInt64(StringSegment value, out long result) |
There was a problem hiding this comment.
I was testing it through TryParseTimeSpan but I'll add some tests specific to this method.
|
Consume the new API from Request/Response.ContentLength |
| if (ch != end) | ||
| { | ||
| result = 0; | ||
| return true; |
| if (ch != end) | ||
| { | ||
| result = 0; | ||
| return true; |
|
🆙📅 |
|
🆙📅 @BrennanConroy @Tratcher @halter73 @davidfowl Here's some performance results tested with ResponseCaching: |
| if (rawValue.Count == 1 && | ||
| !string.IsNullOrWhiteSpace(rawValue[0]) && | ||
| long.TryParse(rawValue[0], styles, CultureInfo.InvariantCulture, out value)) | ||
| HeaderUtilities.TryParseInt64(new StringSegment(rawValue[0]).Trim(), out value)) |
There was a problem hiding this comment.
We should do SetContentLength while we're at it, but I don't know if we have any optimized code for this yet. Kestrel has one but it's for hex.
There was a problem hiding this comment.
Modified the FormatInt64 implementation.
As a reference, formatting long num = 1234567890; to "1234567890" for 30000000 iterations:
Old: 2027ms using num.ToString(CultureInfo.InvariantCulture)
New: 1023ms using FormatInt64(num)
| /// <code>false</code>. | ||
| /// </returns> | ||
| // e.g. { "headerValue=10, targetHeaderValue=30" } | ||
| public static bool TryParseTimeSpan(StringValues headerValues, string targetValue, out TimeSpan? value) |
There was a problem hiding this comment.
This name is misleading, you're not parsing a fully formatted time, only integer seconds. You just happen to represent the output as a TimeSpan. TryParseSeconds?
| { | ||
| for (var i = 0; i < headerValues.Count; i++) | ||
| { | ||
| var index = headerValues[i].IndexOf(targetValue, StringComparison.OrdinalIgnoreCase); |
There was a problem hiding this comment.
This will give you false positives for partial matches
There was a problem hiding this comment.
Not only partial matches, this also doesn't differentiate between name and value. i.e. header: foo=bar would be considered to contain bar where it should only contain foo. This needs a bit of rework.
270cf93 to
bc7e27f
Compare
|
🆙📅 |
| return false; | ||
| } | ||
|
|
||
| private static unsafe bool TryParseInt64FromHeaderValue(int startIndex, string headerValue, out long result) |
There was a problem hiding this comment.
Updated the implementation.
| { | ||
| // Consider using Math.DivRem() | ||
| var quotient = value / 10; | ||
| charBuffer[--position] = (char)(0x30 + (value - quotient * 10)); // 0x30 = '0' |
There was a problem hiding this comment.
Use a char* here so we don't need to load position
There was a problem hiding this comment.
I gave this a try but there isn't any improvement.
9945d6d to
ae2afb2
Compare
- Add allocation free parsing of int32, int64 - Improve performance of converting int64 to string - Add parsing of seconds from header values - Add check for existence of cache directives - Expose CacheControlHeaderValue constants
ae2afb2 to
d50a241
Compare

#737
cc @BrennanConroy @Tratcher