Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

HeaderUtilities enhancements #738

Merged
merged 1 commit into from
Dec 9, 2016
Merged

HeaderUtilities enhancements #738

merged 1 commit into from
Dec 9, 2016

Conversation

JunTaoLuo
Copy link
Contributor

@@ -208,6 +292,82 @@ public static bool TryParseInt64(string value, out long result)
return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
}

internal static unsafe bool TryParseInt32(StringSegment value, out int result)
Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Nov 22, 2016

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

At least add doc comments for the behavior expectations.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea not sure why the old ParseInt32 is internal but I kept the same encapsulation. Should we make that public too?

Copy link
Member

Choose a reason for hiding this comment

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

only if it needs to be

Copy link
Member

Choose a reason for hiding this comment

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

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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";
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently we were using these values in a switch statement and they need to be const for that.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Modified the switch statement. These strings are now public static readonly.

Copy link
Member

Choose a reason for hiding this comment

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

👎 This regressed the code readability and maintainability. These constants aren't worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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";
Copy link
Member

Choose a reason for hiding this comment

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

public static readonly string

@@ -208,6 +292,82 @@ public static bool TryParseInt64(string value, out long result)
return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
}

internal static unsafe bool TryParseInt32(StringSegment value, out int result)
Copy link
Member

Choose a reason for hiding this comment

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

At least add doc comments for the behavior expectations.

@@ -208,6 +292,82 @@ public static bool TryParseInt64(string value, out long result)
return long.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
}

internal static unsafe bool TryParseInt32(StringSegment value, out int result)
Copy link
Member

Choose a reason for hiding this comment

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

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)
Copy link
Member

Choose a reason for hiding this comment

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

tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was testing it through TryParseTimeSpan but I'll add some tests specific to this method.

@Tratcher
Copy link
Member

Consume the new API from Request/Response.ContentLength

if (ch != end)
{
result = 0;
return true;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

false

if (ch != end)
{
result = 0;
return true;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

false

@JunTaoLuo
Copy link
Contributor Author

🆙📅

@JunTaoLuo
Copy link
Contributor Author

JunTaoLuo commented Nov 24, 2016

🆙📅 @BrennanConroy @Tratcher @halter73 @davidfowl

Here's some performance results tested with ResponseCaching:
Before: 323984 RPS, 1379 bytes per request
After: 346946 RPS, 848 bytes per request

long value;
var rawValue = headers[HeaderNames.ContentLength];
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))
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Dec 6, 2016

Choose a reason for hiding this comment

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

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)

Copy link
Member

Choose a reason for hiding this comment

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

/// <code>false</code>.
/// </returns>
// e.g. { "headerValue=10, targetHeaderValue=30" }
public static bool TryParseTimeSpan(StringValues headerValues, string targetValue, out TimeSpan? value)
Copy link
Member

Choose a reason for hiding this comment

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

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);
Copy link
Member

Choose a reason for hiding this comment

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

This will give you false positives for partial matches

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

@JunTaoLuo JunTaoLuo force-pushed the johluo/header-utilities branch from 270cf93 to bc7e27f Compare December 5, 2016 23:49
@JunTaoLuo
Copy link
Contributor Author

🆙📅

cc @Tratcher @halter73 @BrennanConroy

return false;
}

private static unsafe bool TryParseInt64FromHeaderValue(int startIndex, string headerValue, out long result)
Copy link
Member

Choose a reason for hiding this comment

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

Bleh...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the implementation.

{
// Consider using Math.DivRem()
var quotient = value / 10;
charBuffer[--position] = (char)(0x30 + (value - quotient * 10)); // 0x30 = '0'
Copy link
Member

Choose a reason for hiding this comment

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

Use a char* here so we don't need to load position

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I gave this a try but there isn't any improvement.

@JunTaoLuo JunTaoLuo force-pushed the johluo/header-utilities branch from 9945d6d to ae2afb2 Compare December 9, 2016 01:21
- 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
@JunTaoLuo JunTaoLuo force-pushed the johluo/header-utilities branch from ae2afb2 to d50a241 Compare December 9, 2016 01:43
@JunTaoLuo JunTaoLuo merged commit d50a241 into dev Dec 9, 2016
@JunTaoLuo JunTaoLuo deleted the johluo/header-utilities branch December 9, 2016 22:17
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