-
Notifications
You must be signed in to change notification settings - Fork 767
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
Constant Encoded StringValues #64 #68
Conversation
Hi @JunTaoLuo, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution! The agreement was validated by .NET Foundation and real humans are currently evaluating your PR. TTYL, DNFBOT; |
_bytes = null; | ||
} | ||
|
||
public static StringValues CreateConstant(string value, Encoding encoding) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe there is still some contention on the naming of this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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 should have the word "Constant" in it, as constants are a special thing already and this can't create them (despite the intent). I'm happy to just have it be Create
and TryGetConstant
be renamed to TryGetBytes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "CreateXxx" and "TryGetXxx" should have the same "Xxx" in the name. The "Xxx" doesn't have to be "Constant".
That term was chosen originally because of how it was used: internal static class Constants { public static readonly StringValues TextPlain = StringValues.CreateConstant("text/plain"); }
Actually, wasn't it originally CreateBinaryConstant? How about CreateBinary
and TryGetBinary
? Or like you said CreateBytes
and TryGetBytes
?
The problem is: we don't want people to use the Create method unless they are creating a static readonly instance used the same way you use a constant.
If you have someone call StringValues.Create(foo) per-request - as if it was just an alias for the ctor - they'll actually be increasing their allocations per-request. That's why the "Constant" hint was in there: this method is for creating constants...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have this CreateXxx method do you even need the associated constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create/TryGetPreEncoded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, the constructor could be internal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind CreatePreEncoded
/TryGetPreEncoded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Looks good to me, apart from the cc @Tratcher |
/// <param name="bytes">If successful, <paramref name="bytes"/> contains the pre-encoded bytes of the <see cref="StringValues"/>.</param> | ||
/// <param name="encoding">If successful, <paramref name="encoding"/> contains the <see cref="Encoding"/> used to compute the pre-encoded bytes.</param> | ||
/// <returns><c>true</c> if <see cref="StringValues"/> contains pre-encoded bytes, otherwise <c>false</c>.</returns> | ||
public bool TryGetPreEncoded(out byte[] bytes, out Encoding encoding) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method looks kinda gross.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gross by design. Any recommendations for alternative designs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we make EncodedBytes
a public property and add a remark that says its only available for pre-encoded values? Two out
s feels like bad design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to keep them linked than in two separate properties. Otherwise you need to keep explaining the relationship between the two.
Do you have an initial commit for any other repo showing practical usage? Kestrel would consume it, MVC should set it. |
Testing performance for different scenarios for StringValues implementation with class vs struct gave the following results:
Each test configuration was run 5 times without error and the RPS values here are an average of those runs. Unknown Headers used are A few observations. First, although testing in the afternoon showed differences between struct vs class implementations, retesting here indicate otherwise, though class implementation seems to be slightly better in all scenarios. Also, pre-encoding headers seems to have a small benefit in most scenarios but is detrimental for unknown headers in the struct implementation. Full results here: |
909604e
to
4befddb
Compare
Thanks, @JunTaoLuo. These results are interesting, and slightly unexpected. Was the variance between runs small for both the class and the struct cases? |
/// <param name="encoding">The <see cref="Encoding"/> to be use when computing pre-encoded bytes.</param> | ||
/// <returns>A <see cref="StringValues"/> which contains the pre-encoded bytes.</returns> | ||
public static StringValues CreatePreEncoded(string value, Encoding encoding) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should null check the args in this scenario, we actually use them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
It seems like the source of regression here was adding additional fields. Did we experiment with designs where we try to minimize the number of fields? For instance omitting the encoding? |
@rynowak We tried several things. First we verified that making StringValues a class was significantly faster than the 2 field StringValues struct that existed prior to this change. We also tried out @lodejard's suggestion of making StringValues a single-field struct, but that still wasn't quite as fast (and not nearly as clean code-wise) as simply making StringValues a class. The "Struct Implementation" results shown here are from the single-field version of the StringValues struct (which most efficient struct version according to our benchmarks). Those "Struct Implementation" benchmark results are far better than what we have in dev today. You can see the single-field struct version of StringValues here: https://gist.github.com/AspNetSmurfLab/61c40908bb316ca8f42c Please forgive the sloppy coding in the gist, we were just trying to get benchmark results quickly. |
|
||
public StringValues(string value) | ||
{ | ||
_value = value; | ||
_values = null; | ||
_encoding = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need to initialize these anymore.
@halter73 I'd retest the |
Could there be an in place mutator? |
Ignore the mutator comment; it will go horribly wrong :P |
@benaadams The ClearFast change seems to make the difference between the struct/class versions of StrinvValues smaller, but the class version still performs better: StringValues struct:
StringValues class:
|
@halter73 can't argue with that. However, if going class, then mutators would be nice; though
Which night have a perf impact; and null e.g.
What happens with The implicit conversions need to change to accept null values e.g. public static implicit operator string (StringValues values)
{
return values.GetStringValue();
}
public static implicit operator string[] (StringValues value)
{
return value.GetArrayValue();
} needs to change to public static implicit operator string (StringValues values)
{
if (values == null) return null;
return values.GetStringValue();
}
public static implicit operator string[] (StringValues value)
{
if (value == null) return EmptyArray;
return value.GetArrayValue();
} etc... |
I'd like to suggest a hold on this for now; there's a faster CopyFromAscii in Kestrel aspnet/KestrelHttpServer#527 and an even faster one to come when only pooled blocks are used aspnet/KestrelHttpServer#525 as one of the |
5802cb3
to
9020e65
Compare
9020e65
to
4671bc8
Compare
After the many kestrel optimizations, testing the performance of this optimization showed that it makes little difference. I saw ~1% improvement in the plaintext benchmark and after I added 16 more headers the performance improvement was bigger but less stable, hovering between 4-7% (ee aspnet/KestrelHttpServer#584 for more details). Note that the headers added were for illustration only and in real scenarios, it does not make sense to pre-encode all of them. In the end, we decided that this optimization is no longer relevant and will not be made. |
using statement duplicated
Fix #68 - use trace for header logging
Issue #64