-
-
Notifications
You must be signed in to change notification settings - Fork 162
Feature/#258 #271
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
Feature/#258 #271
Changes from 5 commits
b85bc14
9936939
a48fc4c
cfa785f
85bb9f4
01735f0
4803fd0
2f0e481
6568c37
fbe1d1b
369860c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel; | ||
| using System.Linq; | ||
| using JsonApiDotNetCore.Extensions; | ||
|
|
||
| namespace JsonApiDotNetCore.Internal | ||
| { | ||
| public readonly ref struct SpanSplitter | ||
| { | ||
| private readonly ReadOnlySpan<char> _span; | ||
| private readonly List<int> _delimeterIndexes; | ||
| private readonly List<Tuple<int, int>> _substringIndexes; | ||
|
|
||
| public int Count => _substringIndexes.Count(); | ||
| public ReadOnlySpan<char> this[int index] => GetSpanForSubstring(index + 1); | ||
|
|
||
| private SpanSplitter(ref string str, char delimeter) | ||
| { | ||
| _span = str.AsSpan(); | ||
| _delimeterIndexes = str.IndexesOf(delimeter).ToList(); | ||
| _substringIndexes = new List<Tuple<int, int>>(); | ||
| BuildSubstringIndexes(); | ||
| } | ||
|
|
||
| public static SpanSplitter Split(string str, char delimeter) | ||
| { | ||
| return new SpanSplitter(ref str, delimeter); | ||
| } | ||
|
|
||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public override bool Equals(object obj) => throw new NotSupportedException(); | ||
|
|
||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public override int GetHashCode() => throw new NotSupportedException(); | ||
|
|
||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public override string ToString() => throw new NotSupportedException(); | ||
|
|
||
| private ReadOnlySpan<char> GetSpanForSubstring(int substringNumber) | ||
| { | ||
| if (substringNumber > Count) | ||
| { | ||
| throw new ArgumentOutOfRangeException($"There are only {Count} substrings given the delimeter and base string provided"); | ||
| } | ||
|
|
||
| var indexes = _substringIndexes[substringNumber - 1]; | ||
| return _span.Slice(indexes.Item1, indexes.Item2); | ||
| } | ||
|
|
||
| private void BuildSubstringIndexes() | ||
| { | ||
| var start = 0; | ||
| var end = 0; | ||
| foreach (var index in _delimeterIndexes) | ||
| { | ||
| end = index; | ||
| if (start > end) break; | ||
| _substringIndexes.Add(new Tuple<int, int>(start, end - start)); | ||
| start = ++end; | ||
| } | ||
|
|
||
| if (end <= _span.Length) | ||
| { | ||
| _substringIndexes.Add(new Tuple<int, int>(start, _span.Length - start)); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,6 @@ | ||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Extensions; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Internal; | ||||||||||||||||||||||||||
| using Microsoft.AspNetCore.Http; | ||||||||||||||||||||||||||
| using Microsoft.Extensions.Primitives; | ||||||||||||||||||||||||||
|
|
@@ -54,8 +56,10 @@ private static bool IsValidAcceptHeader(HttpContext context) | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private static bool ContainsMediaTypeParameters(string mediaType) | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| var mediaTypeArr = mediaType.Split(';'); | ||||||||||||||||||||||||||
| return (mediaTypeArr[0] == Constants.ContentType && mediaTypeArr.Length == 2); | ||||||||||||||||||||||||||
| const char delimeter = ';'; | ||||||||||||||||||||||||||
| var subSpans = mediaType.SpanSplit(delimeter); | ||||||||||||||||||||||||||
| if (subSpans.Count == 0) return false; | ||||||||||||||||||||||||||
| return subSpans.Count == 2 && subSpans[0].ToString() == Constants.ContentType; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| Method | Mean | Error | StdDev | Gen 0 | Allocated |
|---|---|---|---|---|---|
| UsingSplit | 157.11 ns | 2.9374 ns | 2.6039 ns | 0.2134 | 336 B |
| UsingSpan (Current PR) | 364.75 ns | 7.1680 ns | 6.3542 ns | 0.2389 | 376 B |
| Proposal (6568c37) | 37.91 ns | 0.7256 ns | 0.6787 ns | - | 0 B |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Builders; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Configuration; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Extensions; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Internal; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Internal.Generics; | ||||||||||||||||||||||||||
| using JsonApiDotNetCore.Internal.Query; | ||||||||||||||||||||||||||
|
|
@@ -64,7 +65,7 @@ public IJsonApiContext ApplyContext<T>(object controller) | |||||||||||||||||||||||||
| throw new JsonApiException(500, $"A resource has not been properly defined for type '{typeof(T)}'. Ensure it has been registered on the ContextGraph."); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var context = _httpContextAccessor.HttpContext; | ||||||||||||||||||||||||||
| var path = context.Request.Path.Value.Split('/'); | ||||||||||||||||||||||||||
| var requestPath = context.Request.Path.Value; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (context.Request.Query.Count > 0) | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
|
|
@@ -75,10 +76,13 @@ public IJsonApiContext ApplyContext<T>(object controller) | |||||||||||||||||||||||||
| var linkBuilder = new LinkBuilder(this); | ||||||||||||||||||||||||||
| BasePath = linkBuilder.GetBasePath(context, _controllerContext.RequestEntity.EntityName); | ||||||||||||||||||||||||||
| PageManager = GetPageManager(); | ||||||||||||||||||||||||||
| IsRelationshipPath = path[path.Length - 2] == "relationships"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var pathSpans = requestPath.SpanSplit('/'); | ||||||||||||||||||||||||||
| IsRelationshipPath = pathSpans[pathSpans.Count - 2].ToString() == "relationships"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| Method | Mean | Error | StdDev | Gen 0 | Allocated |
|---|---|---|---|---|---|
| Original | 421.08 ns | 19.3905 ns | 54.0529 ns | 0.4725 | 744 B |
| Current PR | 697.65 ns | 11.9282 ns | 11.1576 ns | 0.5999 | 944 B |
| Proposal (fbe1d1b) | 52.23 ns | 0.8052 ns | 0.7532 ns | - | 0 B |
Uh oh!
There was an error while loading. Please reload this page.
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.
Interestingly, as written this performs almost identically to the
Splitversion. I've found one optimization that cuts this in half by removing theStringBuilder. I also think we can eliminate most of the allocations withinSpanSplittersince I don't think it needs to be stateful. We really just need to read over a string and find the place where/{entityName}exists (followed by/orEOL), then weSliceeverything before that. So, we shouldn't need to allocate anything until the final.Slice(/*...*/).ToString(). I'll push my changes sometime this weekend.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.
These are the results I get when running benchmarks (will submit code soon):