【问题标题】:Async slower than Sync异步比同步慢
【发布时间】:2023-04-09 22:50:01
【问题描述】:

我一直在处理异步调用,我发现方法的异步版本比同步版本运行得慢得多。任何人都可以评论我可能遗漏的内容。谢谢。

统计

同步方法时间为 00:00:23.5673480

异步方法时间为 00:01:07.1628415

每次调用返回的记录/条目总数 = 19972

下面是我正在运行的代码。

-------- 测试类 ----------

[TestMethod]
public void TestPeoplePerformanceSyncVsAsync()
{
    DateTime start;
    DateTime end;

    start = DateTime.Now;
    for (int i = 0; i < 10; i++)
    {
    using (IPersonRepository repository = kernel.Get<IPersonRepository>())
    {
        IList<IPerson> people1 = repository.GetPeople();
        IList<IPerson> people2 = repository.GetPeople();
    }
    }
    end = DateTime.Now;
    var diff = start - end;
    Console.WriteLine(diff);

    start = DateTime.Now;
    for (int i = 0; i < 10; i++)
    {
    using (IPersonRepository repository = kernel.Get<IPersonRepository>())
    {
        Task<IList<IPerson>> people1 = GetPeopleAsync();
        Task<IList<IPerson>> people2 = GetPeopleAsync();
        Task.WaitAll(new Task[] {people1, people2});
    }
    }
    end = DateTime.Now;
    diff = start - end;
    Console.WriteLine(diff);
}

private async Task<IList<IPerson>> GetPeopleAsync()
{
    using (IPersonRepository repository = kernel.Get<IPersonRepository>())
    {
    return await repository.GetPeopleAsync();
    }
}

-------------- 存储库-------- --------

    public IList<IPerson> GetPeople()
    {
        List<IPerson> people = new List<IPerson>();
        using (PersonContext context = new PersonContext())
        {
            people.AddRange(context.People);
        }
        return people;
    }

    public async Task<IList<IPerson>> GetPeopleAsync()
    {
        List<IPerson> people = new List<IPerson>();
        using (PersonContext context = new PersonContext())
        {
            people.AddRange(await context.People.ToListAsync());
        }
        return people;
    }

【问题讨论】:

  • 使用异步的目的并不严格是为了提高性能。
  • 我不是 c# 专家,但我认为在 GetPeopleAsync 方法中都不应使用“await”,因为它会暂停执行并且这些方法实际上不是异步的。唯一的暂停必须是 Task.WaitAll
  • 我同意,但结果不应该相差那么远,尤其是在数据不小的情况下。
  • @AlexeiKaigorodov 那不正确。那里的await 完全合适。
  • 我同意Servy,我不知道你们怎么了...我发了但我想又是同步错误... Alex的帖子在发帖时出现在我面前...

标签: c# asynchronous task-parallel-library async-await .net-4.5


【解决方案1】:

所以我们在这里遇到了一大堆问题,所以我会直接说这不会是一个详尽的清单。

首先,异步的并不是严格意义上的提高性能。在某些情况下,它可以用来提高性能,但这不是必然它的目标。例如,它还可用于保持 UI 响应。 并行化 通常用于提高性能,但并行化和异步并不等同。除了,并行化还有开销。您正在花时间创建线程、调度它们、在它们之间同步数据等。并行执行某些操作的好处可能会也可能不会超过此开销。如果不是这样,同步解决方案的性能可能会更好。

接下来,您的“异步”示例并非“一直向上”异步。您正在循环内的任务上调用WaitAll。对于正确异步的示例,我们希望看到它一直是异步的,直到 single 操作,即某种形式的消息循环。

接下来,这两者在异步和同步庄园中并不完全相同。他们在做不同的事情,这显然会影响性能:

  • 您的“异步”解决方案创建了 3 个存储库。您的同步解决方案创建了一个。这里会有一些开销。
  • GetPeopleAsync 获取一个列表,然后将所有项目从列表中拉出并放入 another 列表中。这是不必要的开销。

那么你的基准测试有问题:

  • 您正在使用DateTime.Now,它不是为计时操作需要多长时间而设计的。例如,它的精度不是特别高。您应该使用StopWatch 来计算代码花费的时间。
  • 您没有执行那么多迭代。变异有很多机会影响这里的结果。
  • 您没有考虑到前几次运行一段代码需要更长的时间。 JITter 需要“热身”。
  • 垃圾收集可能会影响您的时间安排,即在第一次测试中创建的对象最终可能会在第二次测试中被清理。

【讨论】:

  • 感谢Servy,我相信您提出了有效的观点,特别是存储库实例化。但是,理想情况下,相同方法的同步与异步的总时间不应相差太大。
【解决方案2】:

这可能取决于您的数据,或者更确切地说是数据量。您没有发布您用于运行测试的测试指标,但这是我的经验:

通常,当您期望改进时看到并行算法的性能下降时,加载额外库和产生线程等的开销会减慢并行算法并使其看起来像线性/单-线程版本的性能更好。

更多的数据应该显示更好的性能。当所有库都加载完毕后,也可以尝试运行两次相同的测试,以避免加载开销。

如果您没有看到改进,则说明问题严重。

注意:我猜你被否决了,因为你在 OP 中发布的代码比上下文、指标等多得多。 IMO,很少有 SOers 会真正费心去阅读和摸索那么多代码,而无法执行它,同时还会看到根本没有用的指标!

为什么我没有阅读代码:当我看到带有滚动条的代码块以及原始 OP 中出现的那种文本时,我的大脑会说:不要打扰。我认为很多(如果不是大多数)可能会这样做。

要尝试的事情:

  • 两个不同的同步时间并不意味着具有统计学意义的数据。您应该多次运行每个算法(至少 5 次)以查看您是否遇到异常情况。如果相同算法的结果差异很大,则可能存在其他问题,例如带宽限制、服务器负载等,并且问题是外部问题。
  • 尝试使用 .NET 内存性能和/或内存分析器来帮助您追踪问题。
  • 请参阅@servy 的最佳答案以获取更多线索。看来他实际上花时间更仔细地查看您的代码。

【讨论】:

  • -2 的原因是什么?仅供参考,数据很大,我得到了 AdventureWorks 2012 中使用的 19972 个 Person 对象条目。
  • 好吧,如果我只发布问题,我会因为没有发布代码而再次被否决。而且我发布的代码应该足以查看问题(如果有的话)。
  • @SajidQ:Person 对象的条目数是重要的指标。两个不同的同步时间不是统计上显着的数据。你应该在你的 OP 中提到它。如果可能的话,你也可以减少代码。
  • 列表的大小可能根本不重要。他通过网络获取数据这一事实,即使数据不多,也需要相当长的时间。
  • 我不明白你为什么建议删除一些代码。没有那么多,大部分都很简单,但所有都是相关的。我无法想象在不删除与答案高度相关的重要信息的情况下将其删除。如果您没有时间实际查看代码,那么您真的不应该回答这个问题。您的回答表明您并没有真正打扰,因此您的建议没有用处。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-28
  • 2015-12-02
  • 2012-09-14
  • 1970-01-01
  • 2017-02-21
  • 2019-11-15
相关资源
最近更新 更多