我的 Akka.Net 演示非常慢
My Akka.Net Demo is incredibly slow
我正在尝试获得概念验证 运行 akka.net。我确信我做错了什么,但我不知道它是什么。
我想让我的演员形成一个节点图。稍后,这将是一个复杂的业务对象图,但现在我想尝试这样一个简单的线性结构:
我想向一个节点询问9步远的邻居。我正在尝试以递归方式实现它。我向节点 #9 询问 9 步远的邻居,然后我向节点 #8 询问 8 步远的邻居,依此类推。最后,这应该 return 节点 #0 作为答案。
嗯,我的代码可以运行,但执行时间超过 4 秒。这是为什么?
这是我的完整代码清单:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Akka;
using Akka.Actor;
namespace AkkaTest
{
class Program
{
public static Stopwatch stopwatch = new Stopwatch();
static void Main(string[] args)
{
var system = ActorSystem.Create("MySystem");
IActorRef[] current = new IActorRef[0];
Console.WriteLine("Initializing actors...");
for (int i = 0; i < 10; i++)
{
var current1 = current;
var props = Props.Create<Obj>(() => new Obj(current1, Guid.NewGuid()));
var actorRef = system.ActorOf(props, i.ToString());
current = new[] { actorRef };
}
Console.WriteLine("actors initialized.");
FindNeighboursRequest r = new FindNeighboursRequest(9);
stopwatch.Start();
var response = current[0].Ask(r);
FindNeighboursResponse result = (FindNeighboursResponse)response.Result;
stopwatch.Stop();
foreach (var d in result.FoundNeighbours)
{
Console.WriteLine(d);
}
Console.WriteLine("Search took " + stopwatch.ElapsedMilliseconds + "ms.");
Console.ReadLine();
}
}
public class FindNeighboursRequest
{
public FindNeighboursRequest(int distance)
{
this.Distance = distance;
}
public int Distance { get; private set; }
}
public class FindNeighboursResponse
{
private IActorRef[] foundNeighbours;
public FindNeighboursResponse(IEnumerable<IActorRef> descendants)
{
this.foundNeighbours = descendants.ToArray();
}
public IActorRef[] FoundNeighbours
{
get { return this.foundNeighbours; }
}
}
public class Obj : ReceiveActor
{
private Guid objGuid;
readonly List<IActorRef> neighbours = new List<IActorRef>();
public Obj(IEnumerable<IActorRef> otherObjs, Guid objGuid)
{
this.neighbours.AddRange(otherObjs);
this.objGuid = objGuid;
Receive<FindNeighboursRequest>(r => handleFindNeighbourRequest(r));
}
public Obj()
{
}
private async void handleFindNeighbourRequest (FindNeighboursRequest r)
{
if (r.Distance == 0)
{
FindNeighboursResponse response = new FindNeighboursResponse(new IActorRef[] { Self });
Sender.Tell(response, Self);
return;
}
List<FindNeighboursResponse> responses = new List<FindNeighboursResponse>();
foreach (var actorRef in neighbours)
{
FindNeighboursRequest req = new FindNeighboursRequest(r.Distance - 1);
var response2 = actorRef.Ask(req);
responses.Add((FindNeighboursResponse)response2.Result);
}
FindNeighboursResponse response3 = new FindNeighboursResponse(responses.SelectMany(rx => rx.FoundNeighbours));
Sender.Tell(response3, Self);
}
}
}
这种缓慢行为的原因是您使用 Ask 的方式(您使用它,但我稍后会介绍)。在您的示例中,您在循环中询问每个邻居,然后立即执行 response2.Result
主动阻塞当前参与者(及其所在的线程)。因此,您实际上是在使用阻塞进行同步流。
解决这个问题最简单的方法是收集从 Ask
返回的所有任务并使用 Task.WhenAll
收集所有任务,而不是在循环中等待每个任务。举个例子:
public class Obj : ReceiveActor
{
private readonly IActorRef[] _neighbours;
private readonly Guid _id;
public Obj(IActorRef[] neighbours, Guid id)
{
_neighbours = neighbours;
_id = id;
Receive<FindNeighboursRequest>(async r =>
{
if (r.Distance == 0) Sender.Tell(new FindNeighboursResponse(new[] {Self}));
else
{
var request = new FindNeighboursRequest(r.Distance - 1);
var replies = _neighbours.Select(neighbour => neighbour.Ask<FindNeighboursResponse>(request));
var ready = await Task.WhenAll(replies);
var responses = ready.SelectMany(x => x.FoundNeighbours);
Sender.Tell(new FindNeighboursResponse(responses.ToArray()));
}
});
}
}
这个要快得多。
注意:通常你不应该在 actor 内部使用 Ask:
- 每个请求都会在当前 actor 中分配一个监听器,所以通常使用
Ask
比使用 Tell
. 传递消息要重很多
- 当通过 actor 链发送消息时,ask 的成本是通过每个 actor 额外传输消息两次(一次用于请求,一次用于回复)。一种流行的模式是,当您从 A⇒B⇒C⇒D 发送请求并从 D 返回 A 时,您可以直接回复 D⇒A,而无需通过整个链返回消息。通常
Forward
/Tell
的组合效果更好。
- 如果没有必要,通常不要使用 Receive 的异步版本 - 目前,与同步版本相比,Actor 的速度较慢。
我正在尝试获得概念验证 运行 akka.net。我确信我做错了什么,但我不知道它是什么。
我想让我的演员形成一个节点图。稍后,这将是一个复杂的业务对象图,但现在我想尝试这样一个简单的线性结构:
我想向一个节点询问9步远的邻居。我正在尝试以递归方式实现它。我向节点 #9 询问 9 步远的邻居,然后我向节点 #8 询问 8 步远的邻居,依此类推。最后,这应该 return 节点 #0 作为答案。
嗯,我的代码可以运行,但执行时间超过 4 秒。这是为什么?
这是我的完整代码清单:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Akka;
using Akka.Actor;
namespace AkkaTest
{
class Program
{
public static Stopwatch stopwatch = new Stopwatch();
static void Main(string[] args)
{
var system = ActorSystem.Create("MySystem");
IActorRef[] current = new IActorRef[0];
Console.WriteLine("Initializing actors...");
for (int i = 0; i < 10; i++)
{
var current1 = current;
var props = Props.Create<Obj>(() => new Obj(current1, Guid.NewGuid()));
var actorRef = system.ActorOf(props, i.ToString());
current = new[] { actorRef };
}
Console.WriteLine("actors initialized.");
FindNeighboursRequest r = new FindNeighboursRequest(9);
stopwatch.Start();
var response = current[0].Ask(r);
FindNeighboursResponse result = (FindNeighboursResponse)response.Result;
stopwatch.Stop();
foreach (var d in result.FoundNeighbours)
{
Console.WriteLine(d);
}
Console.WriteLine("Search took " + stopwatch.ElapsedMilliseconds + "ms.");
Console.ReadLine();
}
}
public class FindNeighboursRequest
{
public FindNeighboursRequest(int distance)
{
this.Distance = distance;
}
public int Distance { get; private set; }
}
public class FindNeighboursResponse
{
private IActorRef[] foundNeighbours;
public FindNeighboursResponse(IEnumerable<IActorRef> descendants)
{
this.foundNeighbours = descendants.ToArray();
}
public IActorRef[] FoundNeighbours
{
get { return this.foundNeighbours; }
}
}
public class Obj : ReceiveActor
{
private Guid objGuid;
readonly List<IActorRef> neighbours = new List<IActorRef>();
public Obj(IEnumerable<IActorRef> otherObjs, Guid objGuid)
{
this.neighbours.AddRange(otherObjs);
this.objGuid = objGuid;
Receive<FindNeighboursRequest>(r => handleFindNeighbourRequest(r));
}
public Obj()
{
}
private async void handleFindNeighbourRequest (FindNeighboursRequest r)
{
if (r.Distance == 0)
{
FindNeighboursResponse response = new FindNeighboursResponse(new IActorRef[] { Self });
Sender.Tell(response, Self);
return;
}
List<FindNeighboursResponse> responses = new List<FindNeighboursResponse>();
foreach (var actorRef in neighbours)
{
FindNeighboursRequest req = new FindNeighboursRequest(r.Distance - 1);
var response2 = actorRef.Ask(req);
responses.Add((FindNeighboursResponse)response2.Result);
}
FindNeighboursResponse response3 = new FindNeighboursResponse(responses.SelectMany(rx => rx.FoundNeighbours));
Sender.Tell(response3, Self);
}
}
}
这种缓慢行为的原因是您使用 Ask 的方式(您使用它,但我稍后会介绍)。在您的示例中,您在循环中询问每个邻居,然后立即执行 response2.Result
主动阻塞当前参与者(及其所在的线程)。因此,您实际上是在使用阻塞进行同步流。
解决这个问题最简单的方法是收集从 Ask
返回的所有任务并使用 Task.WhenAll
收集所有任务,而不是在循环中等待每个任务。举个例子:
public class Obj : ReceiveActor
{
private readonly IActorRef[] _neighbours;
private readonly Guid _id;
public Obj(IActorRef[] neighbours, Guid id)
{
_neighbours = neighbours;
_id = id;
Receive<FindNeighboursRequest>(async r =>
{
if (r.Distance == 0) Sender.Tell(new FindNeighboursResponse(new[] {Self}));
else
{
var request = new FindNeighboursRequest(r.Distance - 1);
var replies = _neighbours.Select(neighbour => neighbour.Ask<FindNeighboursResponse>(request));
var ready = await Task.WhenAll(replies);
var responses = ready.SelectMany(x => x.FoundNeighbours);
Sender.Tell(new FindNeighboursResponse(responses.ToArray()));
}
});
}
}
这个要快得多。
注意:通常你不应该在 actor 内部使用 Ask:
- 每个请求都会在当前 actor 中分配一个监听器,所以通常使用
Ask
比使用Tell
. 传递消息要重很多
- 当通过 actor 链发送消息时,ask 的成本是通过每个 actor 额外传输消息两次(一次用于请求,一次用于回复)。一种流行的模式是,当您从 A⇒B⇒C⇒D 发送请求并从 D 返回 A 时,您可以直接回复 D⇒A,而无需通过整个链返回消息。通常
Forward
/Tell
的组合效果更好。 - 如果没有必要,通常不要使用 Receive 的异步版本 - 目前,与同步版本相比,Actor 的速度较慢。