-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Remove Uri.TryParse
and replace with Span<char>
parsing
#5039
Remove Uri.TryParse
and replace with Span<char>
parsing
#5039
Conversation
|
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
ActorPath_Parse | 1,672.00 ns | 9.184 ns | 7.669 ns | 0.2136 | - | - | 896 B |
ActorPath_Concat | 54.91 ns | 1.128 ns | 1.299 ns | 0.0268 | - | - | 112 B |
ActorPath_Equals | 26.08 ns | 0.095 ns | 0.085 ns | - | - | - | - |
ActorPath_ToString | 119.24 ns | 0.835 ns | 0.740 ns | 0.0210 | - | - | 88 B |
ActorPath.Parse
After
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19041.985 (2004/May2020Update/20H1)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=5.0.203
[Host] : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
DefaultJob : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
ActorPath_Parse | 517.56 ns | 7.238 ns | 5.651 ns | 0.1392 | - | - | 584 B |
ActorPath_Concat | 63.75 ns | 1.323 ns | 1.173 ns | 0.0267 | - | - | 112 B |
ActorPath_Equals | 20.58 ns | 0.433 ns | 0.464 ns | - | - | - | - |
ActorPath_ToString | 212.42 ns | 2.703 ns | 2.528 ns | 0.0515 | - | - | 216 B |
ActorPath_ToSerializationFormat | 234.79 ns | 2.901 ns | 2.265 ns | 0.0515 | - | - | 216 B |
ActorPath_ToSerializationFormatWithAddress | 245.78 ns | 4.985 ns | 6.122 ns | 0.0515 | - | - | 216 B |
src/core/Akka/Actor/ActorPath.cs
Outdated
{ | ||
// Protocol must start with 'akka.* | ||
|
||
var fullScheme = spanified.Slice(0, firstColonPos).ToString().ToLowerInvariant(); |
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.
Stupid questions, can we use stackalloc
here to avoid allocating two 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.
yeah there should be a way to do the ToLower
part without allocating a second string
Ah, forgot to test IPV6 and IPV4 addresses explicitly |
|
Final before / after numbers, starting from
|
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
ActorPath_Parse | 1,938.91 ns | 5.334 ns | 4.454 ns | 0.2670 | - | - | 1,128 B |
ActorPath_Concat | 59.56 ns | 0.876 ns | 0.819 ns | 0.0421 | - | - | 176 B |
ActorPath_Equals | 24.97 ns | 0.090 ns | 0.080 ns | - | - | - | - |
ActorPath_ToString | 125.80 ns | 1.200 ns | 1.122 ns | 0.0210 | - | - | 88 B |
ActorPath
After
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19041.985 (2004/May2020Update/20H1)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=5.0.203
[Host] : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
DefaultJob : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
ActorPath_Parse | 558.55 ns | 9.212 ns | 8.617 ns | 0.1392 | - | - | 584 B |
ActorPath_Concat | 57.82 ns | 1.232 ns | 3.022 ns | 0.0267 | - | - | 112 B |
ActorPath_Equals | 19.11 ns | 0.199 ns | 0.176 ns | - | - | - | - |
ActorPath_ToString | 203.28 ns | 2.391 ns | 2.236 ns | 0.0515 | - | - | 216 B |
ActorPath_ToSerializationFormat | 218.22 ns | 3.196 ns | 2.833 ns | 0.0515 | - | - | 216 B |
ActorPath_ToSerializationFormatWithAddress | 237.46 ns | 4.637 ns | 6.029 ns | 0.0515 | - | - | 216 B |
Lost a bit of performance as a result of implementing proper IPV6 checking, but that was necessary.
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
Interesting
That value actually increased with this PR. I'm working on optimizing that next, but I'll have to look into why. |
I wonder if it's because we're already including the UID fragment in our final output now.... |
LOL it's because I made the |
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.
Found some edge cases, might be more but wanted to throw at least these out.
var openBracket = spanified.IndexOf('['); | ||
var closeBracket = spanified.IndexOf(']'); | ||
if (openBracket > -1 && closeBracket > openBracket) |
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 need to have any additional guarding around [] being contained in a path, i.e. possibly misdetecting IPV6 instead of IPV4 here, and then accidentally failing the parse?
One thought is we could pre-check for the slash in the path, and then math based off that index... Would then have to get the slash again later or track it's position as we trim, but would avoid this case if we need to worry about it.
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 the [
and ]
characters are illegal for actor names, so that shouldn't be a problem.
akka.net/src/core/Akka/Actor/ActorPath.cs
Lines 109 to 131 in c663bc2
internal static readonly char[] ValidSymbols = @"""-_.*$+:@&=,!~';""()".ToCharArray(); | |
/// <summary> | |
/// Method that checks if actor name conforms to RFC 2396, http://www.ietf.org/rfc/rfc2396.txt | |
/// Note that AKKA JVM does not allow parenthesis ( ) but, according to RFC 2396 those are allowed, and | |
/// since we use URL Encode to create valid actor names, we must allow them. | |
/// </summary> | |
/// <param name="s">TBD</param> | |
/// <returns>TBD</returns> | |
public static bool IsValidPathElement(string s) | |
{ | |
if (IsNullOrEmpty(s)) | |
{ | |
return false; | |
} | |
return !s.StartsWith("$") && Validate(s); | |
} | |
private static bool IsValidChar(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || | |
(c >= '0' && c <= '9') || ValidSymbols.Contains(c); | |
private static bool IsHexChar(char c) => (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || | |
(c >= '0' && c <= '9'); |
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.
One thought is we could pre-check for the slash in the path, and then math based off that index... Would then have to get the slash again later or track it's position as we trim, but would avoid this case if we need to worry about it.
I think this is a good idea - won't hurt performance much, because if the check fails we stop the parse and return false
anyway. If we are successful we have to retrieve this value anyway in the future.
@@ -327,74 +327,165 @@ public static bool TryParse(string path, out ActorPath actorPath) | |||
{ | |||
actorPath = null; | |||
|
|||
if (!TryParseAddress(path, out var address, out var absoluteUri)) return false; |
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.
NIT: would this technically be a relative URI?
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 can have a relative ActorSelection
but not a relative ActorPath
- we never allowed parsing relative ones before either:
akka.net/src/core/Akka/Actor/ActorPath.cs
Lines 326 to 342 in c663bc2
public static bool TryParse(string path, out ActorPath actorPath) | |
{ | |
actorPath = null; | |
Address address; | |
Uri uri; | |
if (!TryParseAddress(path, out address, out uri)) return false; | |
var pathElements = uri.AbsolutePath.Split('/'); | |
actorPath = new RootActorPath(address) / pathElements.Skip(1); | |
if (uri.Fragment.StartsWith("#")) | |
{ | |
var uid = int.Parse(uri.Fragment.Substring(1)); | |
actorPath = actorPath.WithUid(uid); | |
} | |
return true; | |
} |
This is because in order for the path to compute things like its own string, it has to be able to work its way up the food chain.
src/core/Akka/Actor/ActorPath.cs
Outdated
if (!fullScheme.StartsWith("akka")) | ||
return false; | ||
|
||
spanified = spanified.Slice(firstColonPos + 1); | ||
if (!(spanified[0] == '/' && spanified[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.
Could throw here on a malformed URI on either of these last two lines; may want to consider:
-
For the Slice:
- Maybe a length check with the scheme check?
if (!fullScheme.StartsWith("akka") || spanified.Length < firstColonPos + 2)
(Double check my math here but I think you get the idea)
- Maybe a length check with the scheme check?
-
For the If statement:
if (spanified.Length<2 || !(spanified[0] == '/' && spanified[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.
Got it - I'll add that
Abstraction of ServiceProvider, Improving Akka.DependencyInjection (akkadotnet#4814) * Abstraction of ServiceProvider * introduced non-breaking Akka.DependencyInjection API changes * fixed unit tests / Props bug * fixed up DelegateInjectionSpecs * Added type checking for `Props(Type type, params object[] args)` * fixed non-generic `Props()` method Co-authored-by: Aaron Stannard <aaron@petabridge.com> completed work on ActorPath parsing
f3c2cd4
to
6b2240e
Compare
Final numbers - mostly from safely adding the UID back at the end. We were just string-fying that directly into the actor path before. Maybe that's ok? BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19041.985 (2004/May2020Update/20H1)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=5.0.203
[Host] : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
DefaultJob : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
|
Was able to improve the final BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19041.985 (2004/May2020Update/20H1)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=5.0.203
[Host] : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
DefaultJob : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X64 RyuJIT
|
src/core/Akka/Actor/ActorPath.cs
Outdated
p = p.Parent; | ||
} | ||
|
||
return new string(buffer); | ||
return buffer.ToString(); |
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.
So this method prevents an additional copy of the string buffer from being made, which is why Gen 0 GC is lower for this iteration.
Part of #5030 - largely an experiment