diff --git a/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs
index 02c72881c98..b9885c1290c 100644
--- a/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs
+++ b/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs
@@ -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]
@@ -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);
}
}
}
diff --git a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs
new file mode 100644
index 00000000000..c616f5527b4
--- /dev/null
+++ b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2021 Lightbend Inc.
+// Copyright (C) 2013-2021 .NET Foundation
+//
+//-----------------------------------------------------------------------
+
+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);
+ }
+ }
+}
diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
index 8aee29b1c71..cde266ae92e 100644
--- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
+++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
@@ -115,6 +115,7 @@ namespace Akka.Actor
protected bool SetChildrenTerminationReason(Akka.Actor.Internal.SuspendReason reason) { }
public void SetReceiveTimeout(System.Nullable 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) { }
@@ -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) { }
diff --git a/src/core/Akka.Remote/RemoteSystemDaemon.cs b/src/core/Akka.Remote/RemoteSystemDaemon.cs
index 7393d0aa5b6..1a820b62fb3 100644
--- a/src/core/Akka.Remote/RemoteSystemDaemon.cs
+++ b/src/core/Akka.Remote/RemoteSystemDaemon.cs
@@ -308,10 +308,10 @@ public override IActorRef GetChild(IEnumerable 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));
}
diff --git a/src/core/Akka.Remote/Serialization/LruBoundedCache.cs b/src/core/Akka.Remote/Serialization/LruBoundedCache.cs
index f5b0e9b5111..e2a99fde2f3 100644
--- a/src/core/Akka.Remote/Serialization/LruBoundedCache.cs
+++ b/src/core/Akka.Remote/Serialization/LruBoundedCache.cs
@@ -25,7 +25,7 @@ internal static class FastHash
/// A 32-bit pseudo-random hash value.
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
diff --git a/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs b/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs
index 89a863597a3..49f6152c646 100644
--- a/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs
+++ b/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs
@@ -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]
diff --git a/src/core/Akka/Actor/ActorCell.Children.cs b/src/core/Akka/Actor/ActorCell.Children.cs
index cc38364269d..9c69a74df31 100644
--- a/src/core/Akka/Actor/ActorCell.Children.cs
+++ b/src/core/Akka/Actor/ActorCell.Children.cs
@@ -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;
diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs
index 13b5a00e6b7..0894b4707e5 100644
--- a/src/core/Akka/Actor/ActorCell.cs
+++ b/src/core/Akka/Actor/ActorCell.cs
@@ -479,10 +479,11 @@ protected void SetActorFields(ActorBase actor)
actor?.Unclear();
}
///
- /// TBD
+ /// INTERNAL API
///
/// TBD
/// TBD
+ [Obsolete("Not used. Will be removed in Akka.NET v1.5.")]
public static NameAndUid SplitNameAndUid(string name)
{
var i = name.IndexOf('#');
@@ -491,6 +492,19 @@ public static NameAndUid SplitNameAndUid(string name)
: new NameAndUid(name.Substring(0, i), Int32.Parse(name.Substring(i + 1)));
}
+ ///
+ /// INTERNAL API
+ ///
+ /// The full name of the actor, including the UID if known
+ /// A new (string name, int uid) instance.
+ 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)));
+ }
+
///
/// TBD
///
diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs
index db2a0488988..fa1a730ff36 100644
--- a/src/core/Akka/Actor/ActorPath.cs
+++ b/src/core/Akka/Actor/ActorPath.cs
@@ -276,8 +276,8 @@ public bool Equals(ActorPath other)
/// A newly created
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);
}
///
@@ -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;
@@ -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
{
diff --git a/src/core/Akka/Actor/NameAndUid.cs b/src/core/Akka/Actor/NameAndUid.cs
index 104a45d4a77..fdef5823b4f 100644
--- a/src/core/Akka/Actor/NameAndUid.cs
+++ b/src/core/Akka/Actor/NameAndUid.cs
@@ -5,11 +5,14 @@
//
//-----------------------------------------------------------------------
+using System;
+
namespace Akka.Actor
{
///
- /// TBD
+ /// INTERNAL API
///
+ [Obsolete("Not used. Will be removed in Akka.NET v1.5.")]
public class NameAndUid
{
private readonly string _name;
diff --git a/src/core/Akka/Actor/RepointableActorRef.cs b/src/core/Akka/Actor/RepointableActorRef.cs
index c0e3773a8d9..6933f23da21 100644
--- a/src/core/Akka/Actor/RepointableActorRef.cs
+++ b/src/core/Akka/Actor/RepointableActorRef.cs
@@ -292,12 +292,10 @@ public override IActorRef GetChild(IEnumerable 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));
@@ -305,7 +303,7 @@ public override IActorRef GetChild(IEnumerable name)
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;
}
diff --git a/src/core/Akka/Util/SpanHacks.cs b/src/core/Akka/Util/SpanHacks.cs
new file mode 100644
index 00000000000..147ac8a240c
--- /dev/null
+++ b/src/core/Akka/Util/SpanHacks.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Akka.Util
+{
+ ///
+ /// INTERNAL API.
+ ///
+ /// polyfills that should be deleted once we drop .NET Standard 2.0 support.
+ ///
+ internal static class SpanHacks
+ {
+ ///
+ /// Parses an integer from a string.
+ ///
+ ///
+ /// PERFORMS NO INPUT VALIDATION.
+ ///
+ /// The span of input characters.
+ /// An .
+ public static int Parse(ReadOnlySpan 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;
+ }
+ }
+}