【问题标题】:Using tasks for repeated actions使用任务进行重复操作
【发布时间】:2018-06-08 22:36:53
【问题描述】:

我有 50 个机器学习代理。每一帧,他们都会得到一些输入并计算神经网络。因为每个代理都是独立的,我想让每个代理计算网络作为一个单独的任务。

如果我要为每个代理、每个帧创建一个任务,它会使我的程序变慢。我尝试将我的代理分为 2 个任务(25 和 25),但这仍然是开销。

我看到它的方式是在开始时为 n 组代理创建 n 个线程,并以某种方式在每一帧查询这些线程。一个线程会为一组代理计算网络,然后等待下一个查询。

我已阅读有关此主题的一些文章,但我发现我无法重用任务。那么,有什么解决方法可行呢?

基本上,我对 50 个代理有一个重复的操作,每帧运行大约一分钟,如果不将它们并行化将是一种浪费。

我还是多线程和任务的新手,所以我依赖你的帮助。


旁注:我在 Unity 中使用遗传算法。
这是我尝试将代理分成 n 个组并在 n 个任务中计算它们的网络的代码。
public async Task EvaluateAsync(int groupSize = 10)
{
    var groups = genomes.Select((g, i) => new { Value = g, Index = i })
                        .GroupBy(x => x.Index / groupSize)
                        .Select(x => x.Select(v => v.Value));

    var tasks = groups.Select(g =>
    {
        return Task.Run(() =>
        {
            foreach (var element in g)
                element.Fitness += ComputeFitness(element as NeuralGenome);
        });
    }).ToArray();

    for (var i = 0; i < tasks.Length; i++)
        await tasks[i];
}

在我调用的Update() 函数中:

EvaluateAsync(25).Wait();

网络很大的时候会快一点,但是只有10个神经元的时候会慢很多。

使组更小,只有在网络非常庞大的情况下才会产生更好的性能。

这里我为每个代理创建一个任务:

public async Task EvaluateAsyncEach()
{
    var tasks = genomes.Select(x => Task.Run(() => x.Fitness += ComputeFitness(x as NeuralGenome)))
                       .ToArray();
    foreach (var task in tasks)
        await task;
}

以下测量是针对 10 帧进行的。意思是,t/10 将是完成一项任务的时间。

正常运行时间:

00:00:00.3791190
00:00:00.3758430
00:00:00.3697020
00:00:00.3743900
00:00:00.3764850

每帧每个代理一个任务:

00:00:01.1288240
00:00:01.0761770
00:00:00.9311210
00:00:01.0122570
00:00:00.8938200

25 人一组:

00:00:00.5401100
00:00:00.5629660
00:00:00.5640470
00:00:00.5932220
00:00:00.6053940
00:00:00.5828170

【问题讨论】:

  • 相关:stackoverflow.com/q/18059569/1132334;如果满足两个条件,并行化才有意义:1)如果一个任务正在做足够的工作以使其计划运行的逻辑核心饱和(即大部分时间不让步/等待),那么任务的数量不应超过可用核心数。您的进程也可能不是机器上的唯一进程。 2)完成一项任务的时间应该明显高于任务创建+调度所花费的时间。我怀疑2)是这里的问题。您是否有指标来支持“仍然是开销”的说法?
  • 创建一个任务是相当轻量级的,并且可能与计算神经网络所需的计算能力相形见绌。我认为你在处理错误的问题。
  • @dlatikay 我刚刚添加了时间测量。
  • @JohnWu 我听说并行化强化学习代理很容易。
  • @JohnWu 我想,也许我应该在开始时为 n 组代理创建一个线程,并以某种方式在每帧查询这些线程。

标签: c# multithreading asynchronous async-await


【解决方案1】:

您应该为此使用 Microsoft 的响应式框架。它非常适合这种处理。

代码如下:

var query =
    from genome in genomes.ToObservable()
    from fitness in Observable.Start(() => ComputeFitness(genome as NeuralGenome))
    select new { genome, fitness };

IDisposable subscription =
    query.Subscribe(x => x.genome.Fitness += x.fitness);

它在后台进行所有自己的线程/任务管理。它还会在计算结果后尽快生成结果。

如果你希望能够await得到结果你可以这样做:

var query =
    from genome in genomes.ToObservable()
    from fitness in Observable.Start(() => ComputeFitness(genome as NeuralGenome))
    select new { genome, fitness };

var results = await query.ToArray();

foreach (var x in results)
{
    x.genome.Fitness += x.fitness;
}

只需 NuGet "System.Reactive" 并将 using System.Reactive.Linq; 添加到您的查询中。


根据您评论中的代码,我认为您应该看看这个:

private async Task ComputingNetworksAsync()
{
    var query =
        from a in agents.ToObservable()
        let i = a.GenerateNetworkInputs()
        from n in Observable.Start(() => a.ComputeNetwork(i))
        select n;

    await query.ToArray();
}

这直接等同于您的代码(.ToArray() 除外)。

但是,您可以更进一步,这样做:

private async Task ComputingNetworksAsync()
{
    var query =
        from a in agents.ToObservable()
        from i in Observable.Start(() => a.GenerateNetworkInputs())
        from n in Observable.Start(() => a.ComputeNetwork(i))
        select n;

    await query.ToArray();
}

【讨论】:

  • 运动是在每个“物理帧”中计算的,所以它必须等待......我想。您的第二个示例与使用 Task.Run() 是否相同,如果它正在等待,或者我错过了什么?我实际上有不同的情况,但想法是一样的,所以这是我在每个 Frame 上所做的,但这仍然是一个开销:link
  • 我想@JohnWu 是对的,毕竟我不需要并行化它们
  • @unicornslayer - 我添加了更多代码供您在我的回答中查看。另外,我不认为我的第二个示例是“完全相同的”——它肯定是相似的,因为它试图做同样的事情,但它有一个更清晰的执行模型来执行任务。 Task 方法创建了一堆任务,这些任务会产生您必须等待的副作用。 Observable 方法在 observable 内部没有副作用——所有变化都发生在它之外。这就是您要避免并发问题的原因。
【解决方案2】:

这是一篇好文章。

http://fintechexplained.blogspot.com/2018/05/top-ten-tips-for-implementing-multi.html?m=1

您的解决方案是 PLINQ。避免创建新任务

【讨论】:

  • 请不要链接到外部网站。将内容放入您的答案中,并仅提供支持该内容的链接。
  • 所以你要我复制粘贴整篇文章?我不认为那是正确的做法。这是包含大量有用信息的链接。我们不应该为了复制粘贴而复制粘贴内容。
  • 不,我不想让你复制整篇文章,也不是为了复制。根本不应该抄袭。您应该将自己的内容放在答案中,然后仅链接以支持您的内容。这样做是为了避免在链接站点消失或更改其 URL 等情况下答案被破坏。
  • 我希望您再次阅读我的答案。我提到应该在这里使用 Plinq,然后引用一个链接提到它有更好的信息。如果链接出现故障,为了争论,即使这样,应该选择 Plinq 的答案也在那里。你应该再读一遍答案。
  • 不,想法应该是您的答案包含足够和必要的信息,以便 OP 解决他们的问题无需进行任何进一步阅读或谷歌搜索以获得他们需要什么。一旦您的答案符合该标准,您就可以添加链接。
猜你喜欢
  • 1970-01-01
  • 2021-11-06
  • 2012-08-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-26
  • 2015-12-25
  • 2023-03-04
  • 1970-01-01
相关资源
最近更新 更多