【问题标题】:Why isn't async await improving performance?为什么 async await 不能提高性能?
【发布时间】:2016-11-15 10:08:20
【问题描述】:

我看了这个视频:https://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287。所以我尝试在控制器中实现异步/等待的使用。所以这基本上就是我所做的:

 public class HomeController : Controller
    {
        private static WebClient _webClient = new WebClient();
        public async Task<ActionResult> IndexAsync()
        {
            var data = await _webClient.DownloadStringTaskAsync("http://stackoverflow.com/");
            return View("Index", (object)data);
        }
        public ActionResult Index()
        {
            var data = _webClient.DownloadString("http://stackoverflow.com/");
            return View("Index", (object)data);
        }
    }

然后我使用 Apache Benchmark 并做了以下两个测试:

ab -n 100 -c 100 http://localhost:53446/Home/index

ab -n 100 -c 100 http://localhost:53446/Home/indexasync

我得到了完全相同的性能(我有 8 个 CPU 内核)。这是为什么呢?

【问题讨论】:

  • 您期待什么样的性能提升?
  • 你没有同时运行多个异步操作,所以我预计不会有任何加速。
  • 对于单个请求,您更有可能看到性能下降。异步通过允许处理更多并发请求来减轻负载。
  • -c 参数意味着 100 个并发请求,-n 是请求计数,所以基本上我向我的应用程序发送 100 个并发请求,它将向 SO 执行 100 个并发请求。
  • @goenning:不正确。异步!=并行处理。您可以使用Task.WhenAll 并行运行它们,但如果您只是等待每一行,它们将串行运行。

标签: asp.net-mvc async-await


【解决方案1】:

异步不是关于性能的。那绝对是不正确的。事实上,异步请求的性能通常低于同步,这仅仅是因为异步涉及额外的开销。

使用异步的原因在于高效的资源管理和扩展。一个典型的 Web 服务器进程将有大约 1000 个线程。这通常被称为“最大请求”,因为一个线程通常等于一个请求。如果您有一个 8 核 CPU,理想情况下,每个核心应该有一个进程(在 IIS 中,这些进程称为“网络工作者”)。因此,理论上,您总共可以使用大约 8000 个线程。

这实际上是相当多的,尽管现代网页消耗的请求比大多数人想象的要多。页面本身是一个请求,但该页面将包含图像和外部 JS 和 CSS 文件,所有这些都会生成一个请求,并且通常会使用 AJAX 来进行进一步的请求。关键是,虽然 8000 多个线程仍然存在于您的池中,但如果服务器负载过大,您仍然很可能会用完。

异步只是为您提供超出该限制的喘息空间。在线程进入等待状态的情况下,它可以返回到池中以处理其他请求,同时完成任何外部操作。另一种选择是线程只会闲置(同步)。这就是它的全部。这完全是为了给那些原本空闲的线程分配一些其他的工作,这可能意味着请求排队和超时或被处理之间的差异,即使速度很慢。

【讨论】:

  • 我同意你的观点,但默认情况下 Asp.Net MVC 保留 8 个线程 (msdn.microsoft.com/en-us/library/…),所以在第 8 次请求之后,我的第 9 次和第 10 次应该会慢一些,因为 asp.net 会在创建一个新的之前等待 Xms(我认为是 500,但不确定)。
  • 您在这里的假设是不正确的。启动线程有一些开销,所以这个设置作为一种预先加载一定数量的方式而存在。但是,线程是存在还是创建并不真正重要,而且对于同步和异步之间的区别当然也不重要,因为无论哪种方式都必须发生。只有当所有可用线程都在运行时,您才会开始看到同步和异步之间的任何区别,无论它们是如何或何时创建的。
  • 我仍然不明白 channel9 视频的性能如何提高。感谢您的宝贵时间
  • 好吧,我还没有看过这个视频,现在也没有一个半小时的空闲时间,所以我无法对此发表评论。
【解决方案2】:

在本地机器上很难运行耗尽线程池的负载测试。 假装通过人为限制线程池耗尽它要容易得多,就像我做的in my gist

protected void Application_Start()
{
  int workerThreads, ioThreads;
  ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
  ThreadPool.SetMaxThreads(Environment.ProcessorCount, ioThreads);
  ...
}

【讨论】:

    【解决方案3】:

    有几个突出的原因。

    来自Using Asynchronous Methods in ASP.NET MVC 4

    线程池中的线程数是有限的(.NET 4.5 的默认最大值为 5,000)。在具有高并发长时间运行请求的大型应用程序中,所有可用线程可能都很忙。这种情况称为线程饥饿。

    因此,一次运行 100 个请求甚至不会开始让您的线程饿死。

    此外,一个简单的 GET 请求将运行得非常快。执行需要数秒甚至数分钟的操作的测试将获得更明显的性能提升。

    【讨论】:

    • 限制非常高是的,但据我了解,默认情况下,这5000个线程不会添加到池中。当您要求更多时会添加线程,这会导致请求排队。
    • 我刚刚尝试了一个大文件 (42mb),但在异步和同步中仍然得到相同的结果
    • 改进更多的是可扩展性和内存,而不是执行速度。两者之间的内存占用量是否发生变化?如果您将请求数量增加到 1000 或更多怎么办?这篇文章很好地解释了 async/await 如何减少内存使用:msdn.microsoft.com/en-us/magazine/dn802603.aspx
    • 下载大文件并不重要。异步部分是发送请求并等待服务器的响应。一旦您的服务器使用响应,它就会主动需要该线程。
    • 确实我会尝试两台测试服务器
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-26
    • 2021-08-29
    • 1970-01-01
    • 2023-01-17
    • 2015-07-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多