-
Notifications
You must be signed in to change notification settings - Fork 191
Conversation
e20df05
to
248d389
Compare
@@ -321,7 +321,7 @@ private void SetDate(string parameter, DateTimeOffset? date) | |||
else | |||
{ | |||
// Must always be quoted | |||
var dateString = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", HttpRuleParser.DateToString(date.Value)); | |||
var dateString = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", HeaderUtilities.FormatDate(date.Value)); |
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.
Why isn't the quoting part of the formatting?
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 this might be a special requirement for Content-Disposition
? @Tratcher?
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.
Correct, the date only needs to be quoted in some contexts like CDHV
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.
Maybe it worth adding quoted
argument to HeaderUtilities.FormatDate
?
{ | ||
var offset = 0; | ||
char* target = stackalloc char[Rfc1123DateLength]; | ||
var universalDateTime = dateTime.ToUniversalTime(); |
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 may be possible to use dateTime.UtcDateTime
to save the extra allocation that ToUniversalTime()
does, but I would test perf for both in case DateTimeOffset
is better for this scenario than DateTime
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.
Doesn't ToUniversalTime()
return a struct?
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.
Yeah, ToUniversalTime
just returns another DateTimeOffset
using UtcDateTime
; http://referencesource.microsoft.com/#mscorlib/system/datetimeoffset.cs,683
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.
Do we really need to convert if input is already utc?
if (dateTime.Offset > TimeSpan.Zero)
dateTime = dateTime.ToUniversalTime();
|
||
namespace Microsoft.Net.Http.Headers | ||
{ | ||
internal static class DateTimeFormatter |
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'm unsure of the level of optimization that's really required for this code, but a few off the cuff thoughts:
- My intuition says there's gotta be a better way of copying the bytes than a
foreach
- It might be beneficial to 'pack' all of these bytes into an array + offset for locality
- We should look into the inlinability of all of this (most related to 1)
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.
What about now?
@hallatore did some testing with Buffer.Memcpy
earlier today. It didn't change much. He also verified that stuff was inlined nicely.
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 was testing changing to foreach to use Buffer.memcpy, but the results where identical.
Ref memcpy: https://gist.github.com/hallatore/a10ffd0d69a508d09b74a4f720c0511a
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 could also consider vectorizing this where the lengths are known, using (int *)
or (long *)
. I think that what's there is probably fine, and this is important it would come up again.
private static readonly byte[] SepBytes = UTF8.GetBytes(GetMonthName(9)); | ||
private static readonly byte[] OctBytes = UTF8.GetBytes(GetMonthName(10)); | ||
private static readonly byte[] NovBytes = UTF8.GetBytes(GetMonthName(11)); | ||
private static readonly byte[] DecBytes = UTF8.GetBytes(GetMonthName(12)); |
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.
Why do you store these as bytes and then cast back to chars?
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.
Uuh. That's a good question. I've been back and forth between char*
and byte*
a couple of times because of missing APIs for different targets. I'll revert back to char[]
.
{ | ||
switch (dayOfWeek) | ||
{ | ||
case DayOfWeek.Sunday: return Format(SunBytes, target, offset); |
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.
Store as array and use indexing instead of switch
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 want these in an array of arrays? 😄
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.
Why not array of strings?
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.
Uhhh, yes, of course. I guess we could just reuse DateTimeFormatInfo.AbbreviatedDayNames
and DateTimeFormatInfo.AbbreviatedMonthNames
.
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.
Did you try this measuring packing all of these into a single array? Then your switch just returns an offset into the array.
For instance your format could be like:
M O N , T U E , W E D , ...
Then you can just cast to `(int *) and blit 4 bytes at once including the comma
{ | ||
switch (month) | ||
{ | ||
case 1: return Format(JanBytes, target, offset); |
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.
Indexing.
That got rid of quite a bit of code 😝 |
|
||
private static unsafe int FormatDayOfWeek(DayOfWeek dayOfWeek, char* target, int offset) | ||
{ | ||
return Format(FormatInfo.AbbreviatedDayNames[(int) dayOfWeek], target, offset); |
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.
Could we cache this array and similarly for the month names
See http://referencesource.microsoft.com/#mscorlib/system/globalization/datetimeformatinfo.cs,1393 for what it does every get
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.
Where did additional allocations come from?
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.
Probably a string enumerator. Let me get rid of that foreach
loop 😉
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.
private static readonly string[] MonthNames = FormatInfo.AbbreviatedMonthNames; | ||
|
||
// The format is "ddd, dd MMM yyyy HH:mm:ss GMT". | ||
private const int Rfc1123DateLength = 29; |
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.
nitpicking but you could have done Rfc1123DateLength = "ddd, dd MMM yyyy HH:mm:ss GMT".Length
Alright. Here's the current state of this PR: About ~60% faster, and ~85% less allocations. There are other, faster alternatives, but I'm not sure how far you'd like to stretch; https://gist.github.com/aL3891/2644c97e55f2f6c67b1ade15228031b8 |
5ba9c76
to
df88a1c
Compare
Where did the extra 3 bytes come from? |
@BrennanConroy It varies a bit from run to run. Not really sure why. I think it might average out the bytes allocated for the statics on the first op. @mattwarren? EDIT: Yeah, looks like it takes the total amount of bytes allocated and divides it by the total number of operations. I think the number of ops can vary for each run. https://github.com/PerfDotNet/BenchmarkDotNet/blob/b35d523dfc859cc0f94897be124e675b79f74845/src/BenchmarkDotNet.Diagnostics.Windows/MemoryDiagnoser.cs#L160 |
@khellang we have multiple PR's commit with similar use case of string construction, can you please try using dotnet/extensions#157 to format the date time so we can have common pattern in both of them? |
@pakrym I already have it sitting here; khellang@6d9345f. Ran a quick benchmar and it looks like it regressed perf slightly (not sure why), but I think it's nice for consistency. Want me to include it in this PR? |
The allocations are still the same, though. That was the main point of this PR (and related issue) before things turned into a perf contest 😛 |
@khellang would be awesome. |
0c806c2
to
7e454c9
Compare
🆙📅 Moved to |
7e454c9
to
bd26df3
Compare
{ | ||
var universal = dateTime.UtcDateTime; | ||
|
||
var length = quoted ? QuotedRfc1123DateLength : Rfc1123DateLength; |
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.
QuotedRfc1123DateLength is only used once, you might as well put the +2 here.
@@ -24,6 +24,7 @@ | |||
"frameworks": { | |||
"netstandard1.1": { | |||
"dependencies": { | |||
"Microsoft.Extensions.Primitives": "1.1.0-*", |
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 goes under the root dependencies, it's not framework specific.
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.
What about System.Buffers
? That's not framework specific either?
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.
Move them all
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.
Shouldn't NETStandard.Library
be moved to be framework-specific? Like khellang@b5f8a8d?
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.
Why?
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.
Doesn't it pull down lots of stuff you don't need when you target net451
?
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.
Sure, once
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.
But does it hurt to move it under netstandard
? Is this what you're recommending library authors do?
Thank you @khellang ! |
Closes #715
Benchmark Results
About ~60% faster, and ~85% less allocations 😁