Skip to content
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

Span<char>-ifying ActorPath and Address parsing #5030

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs
Expand Up @@ -16,12 +16,16 @@ public class ActorPathBenchmarks
{
private ActorPath x;
private ActorPath y;
private ActorPath _childPath;
private Address _sysAdr = new Address("akka.tcp", "system", "127.0.0.1", 1337);
private Address _otherAdr = new Address("akka.tcp", "system", "127.0.0.1", 1338);

[GlobalSetup]
public void Setup()
{
x = new RootActorPath(new Address("akka.tcp", "system", "127.0.0.1", 1337), "user");
y = new RootActorPath(new Address("akka.tcp", "system", "127.0.0.1", 1337), "system");
x = new RootActorPath(_sysAdr, "user");
y = new RootActorPath(_sysAdr, "system");
_childPath = x / "parent" / "child";
}

[Benchmark]
Expand All @@ -45,7 +49,19 @@ public bool ActorPath_Equals()
[Benchmark]
public string ActorPath_ToString()
{
return x.ToString();
return _childPath.ToString();
}

[Benchmark]
public string ActorPath_ToSerializationFormat()
{
return _childPath.ToSerializationFormat();
}

[Benchmark]
public string ActorPath_ToSerializationFormatWithAddress()
{
return _childPath.ToSerializationFormatWithAddress(_otherAdr);
}
}
}
34 changes: 34 additions & 0 deletions src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs
@@ -0,0 +1,34 @@
//-----------------------------------------------------------------------
// <copyright file="NameAndUidBenchmarks.cs" company="Akka.NET Project">
// Copyright (C) 2009-2021 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2021 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using Akka.Actor;
using Akka.Benchmarks.Configurations;
using BenchmarkDotNet.Attributes;

namespace Akka.Benchmarks.Actor
{
[Config(typeof(MicroBenchmarkConfig))]
public class NameAndUidBenchmarks
{
public const string ActorPath = "foo#11241311";

[Benchmark]
public NameAndUid ActorCell_SplitNameAndUid()
{
return ActorCell.SplitNameAndUid(ActorPath);
}

[Benchmark]
public (string name, int uid) ActorCell_GetNameAndUid()
{
return ActorCell.GetNameAndUid(ActorPath);
}
}
}
2 changes: 2 additions & 0 deletions src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
Expand Up @@ -115,6 +115,7 @@ namespace Akka.Actor
protected bool SetChildrenTerminationReason(Akka.Actor.Internal.SuspendReason reason) { }
public void SetReceiveTimeout(System.Nullable<System.TimeSpan> timeout = null) { }
protected void SetTerminated() { }
[System.ObsoleteAttribute("Not used. Will be removed in Akka.NET v1.5.")]
public static Akka.Actor.NameAndUid SplitNameAndUid(string name) { }
public virtual void Start() { }
protected void Stash(Akka.Dispatch.SysMsg.SystemMessage msg) { }
Expand Down Expand Up @@ -1325,6 +1326,7 @@ namespace Akka.Actor
public override void Suspend() { }
protected override void TellInternal(object message, Akka.Actor.IActorRef sender) { }
}
[System.ObsoleteAttribute("Not used. Will be removed in Akka.NET v1.5.")]
public class NameAndUid
{
public NameAndUid(string name, int uid) { }
Expand Down
6 changes: 3 additions & 3 deletions src/core/Akka.Remote/RemoteSystemDaemon.cs
Expand Up @@ -308,10 +308,10 @@ public override IActorRef GetChild(IEnumerable<string> name)
var n = 0;
while (true)
{
var nameAndUid = ActorCell.SplitNameAndUid(path);
if (TryGetChild(nameAndUid.Name, out var child))
var (s, uid) = ActorCell.GetNameAndUid(path);
if (TryGetChild(s, out var child))
{
if (nameAndUid.Uid != ActorCell.UndefinedUid && nameAndUid.Uid != child.Path.Uid)
if (uid != ActorCell.UndefinedUid && uid != child.Path.Uid)
return Nobody.Instance;
return n == 0 ? child : child.GetChild(name.TakeRight(n));
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/Akka.Remote/Serialization/LruBoundedCache.cs
Expand Up @@ -25,7 +25,7 @@ internal static class FastHash
/// <returns>A 32-bit pseudo-random hash value.</returns>
public static int OfString(string s)
{
var chars = s.ToCharArray();
var chars = s.AsSpan();
var s0 = 391408L; // seed value 1, DON'T CHANGE
var s1 = 601258L; // seed value 2, DON'T CHANGE
unchecked
Expand Down
6 changes: 2 additions & 4 deletions src/core/Akka.Tests/Actor/ActorSelectionSpec.cs
Expand Up @@ -76,12 +76,10 @@ private IActorRef AskNode(IActorRef node, IQuery query)
{
var result = node.Ask(query).Result;

var actorRef = result as IActorRef;
if (actorRef != null)
if (result is IActorRef actorRef)
return actorRef;

var selection = result as ActorSelection;
return selection != null ? Identify(selection) : null;
return result is ActorSelection selection ? Identify(selection) : null;
}

[Fact]
Expand Down
7 changes: 3 additions & 4 deletions src/core/Akka/Actor/ActorCell.Children.cs
Expand Up @@ -387,17 +387,16 @@ public bool TryGetSingleChild(string name, out IInternalActorRef child)
}
else
{
var nameAndUid = SplitNameAndUid(name);
if (TryGetChildRestartStatsByName(nameAndUid.Name, out var stats))
var (s, uid) = GetNameAndUid(name);
if (TryGetChildRestartStatsByName(s, out var stats))
{
var uid = nameAndUid.Uid;
if (uid == ActorCell.UndefinedUid || uid == stats.Uid)
{
child = stats.Child;
return true;
}
}
else if (TryGetFunctionRef(nameAndUid.Name, nameAndUid.Uid, out var functionRef))
else if (TryGetFunctionRef(s, uid, out var functionRef))
{
child = functionRef;
return true;
Expand Down
16 changes: 15 additions & 1 deletion src/core/Akka/Actor/ActorCell.cs
Expand Up @@ -479,10 +479,11 @@ protected void SetActorFields(ActorBase actor)
actor?.Unclear();
}
/// <summary>
/// TBD
/// INTERNAL API
/// </summary>
/// <param name="name">TBD</param>
/// <returns>TBD</returns>
[Obsolete("Not used. Will be removed in Akka.NET v1.5.")]
public static NameAndUid SplitNameAndUid(string name)
{
var i = name.IndexOf('#');
Expand All @@ -491,6 +492,19 @@ public static NameAndUid SplitNameAndUid(string name)
: new NameAndUid(name.Substring(0, i), Int32.Parse(name.Substring(i + 1)));
}

/// <summary>
/// INTERNAL API
/// </summary>
/// <param name="name">The full name of the actor, including the UID if known</param>
/// <returns>A new (string name, int uid) instance.</returns>
internal static (string name, int uid) GetNameAndUid(string name)
{
var i = name.IndexOf('#');
return i < 0
? (name, UndefinedUid)
: (name.Substring(0, i), SpanHacks.Parse(name.AsSpan(i + 1)));
}

/// <summary>
/// TBD
/// </summary>
Expand Down
35 changes: 26 additions & 9 deletions src/core/Akka/Actor/ActorPath.cs
Expand Up @@ -276,8 +276,8 @@ public bool Equals(ActorPath other)
/// <returns>A newly created <see cref="ChildActorPath"/></returns>
public static ActorPath operator /(ActorPath path, string name)
{
var nameAndUid = ActorCell.SplitNameAndUid(name);
return new ChildActorPath(path, nameAndUid.Name, nameAndUid.Uid);
var (s, uid) = ActorCell.GetNameAndUid(name);
return new ChildActorPath(path, s, uid);
}

/// <summary>
Expand Down Expand Up @@ -327,15 +327,31 @@ public static bool TryParse(string path, out ActorPath actorPath)
{
actorPath = null;

if (!TryParseAddress(path, out var address, out var uri)) return false;
var spanified = uri.AbsolutePath.AsSpan();
var nextSlash = 0;

actorPath = new RootActorPath(address);

do
{
nextSlash = spanified.IndexOf('/');
if (nextSlash > 0)
{
actorPath /= spanified.Slice(0, nextSlash).ToString();
}
else if (nextSlash < 0 && spanified.Length > 0)
{
actorPath /= spanified.ToString();
}

spanified = spanified.Slice(nextSlash + 1);
} while (nextSlash >= 0);


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));
var uid = SpanHacks.Parse(uri.Fragment.AsSpan(1));
actorPath = actorPath.WithUid(uid);
}
return true;
Expand Down Expand Up @@ -378,13 +394,14 @@ private static bool TryParseAddress(string path, out Address address, out Uri ur
//port may not be specified for these types of paths
return false;
}

//System name is in the "host" position. According to rfc3986 host is case
//insensitive, but should be produced as lowercase, so if we use uri.Host
//we'll get it in lower case.
//So we'll extract it ourselves using the original path.
//We skip the protocol and "://"
var systemNameLength = uri.Host.Length;
systemName = path.Substring(protocol.Length + 3, systemNameLength);
systemName = path.AsSpan().Slice(protocol.Length + 3, systemNameLength).ToString();
}
else
{
Expand Down
5 changes: 4 additions & 1 deletion src/core/Akka/Actor/NameAndUid.cs
Expand Up @@ -5,11 +5,14 @@
// </copyright>
//-----------------------------------------------------------------------

using System;

namespace Akka.Actor
{
/// <summary>
/// TBD
/// INTERNAL API
/// </summary>
[Obsolete("Not used. Will be removed in Akka.NET v1.5.")]
public class NameAndUid
{
private readonly string _name;
Expand Down
10 changes: 4 additions & 6 deletions src/core/Akka/Actor/RepointableActorRef.cs
Expand Up @@ -292,20 +292,18 @@ public override IActorRef GetChild(IEnumerable<string> name)
case "":
return ActorRefs.Nobody;
default:
var nameAndUid = ActorCell.SplitNameAndUid(next);
if (Lookup.TryGetChildStatsByName(nameAndUid.Name, out var stats))
var (s, uid) = ActorCell.GetNameAndUid(next);
if (Lookup.TryGetChildStatsByName(s, out var stats))
{
var crs = stats as ChildRestartStats;
var uid = nameAndUid.Uid;
if (crs != null && (uid == ActorCell.UndefinedUid || uid == crs.Uid))
if (stats is ChildRestartStats crs && (uid == ActorCell.UndefinedUid || uid == crs.Uid))
{
if (name.Skip(1).Any())
return crs.Child.GetChild(name.Skip(1));
else
return crs.Child;
}
}
else if (Lookup is ActorCell cell && cell.TryGetFunctionRef(nameAndUid.Name, nameAndUid.Uid, out var functionRef))
else if (Lookup is ActorCell cell && cell.TryGetFunctionRef(s, uid, out var functionRef))
{
return functionRef;
}
Expand Down
48 changes: 48 additions & 0 deletions src/core/Akka/Util/SpanHacks.cs
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Akka.Util
{
/// <summary>
/// INTERNAL API.
///
/// <see cref="Span{T}"/> polyfills that should be deleted once we drop .NET Standard 2.0 support.
/// </summary>
internal static class SpanHacks
{
/// <summary>
/// Parses an integer from a string.
/// </summary>
/// <remarks>
/// PERFORMS NO INPUT VALIDATION.
/// </remarks>
/// <param name="str">The span of input characters.</param>
/// <returns>An <see cref="int"/>.</returns>
public static int Parse(ReadOnlySpan<char> str)
{
var pos = 0;
var returnValue = 0;
var sign = 1;
if (str[0] == '-')
{
sign = -1;
pos++;
}

bool IsNumeric(char x)
{
return (x >= '0' && x <= '9');
}

for (; pos < str.Length; pos++)
{
if (!IsNumeric(str[pos]))
throw new FormatException($"[{str.ToString()}] is now a valid numeric format");
returnValue = returnValue * 10 + str[pos] - '0';
}

return sign * returnValue;
}
}
}