async python_Async Python 竟不比sync Python 快,怎么回事?

【CSDN编者按】在实际的基准测试下,async (异步)Python比“sync”(同步) Python要慢。而更让人担心的是,async框架在负载下会不稳定。

作者 | Cal Paterson

译者 | 香槟超新星,责编 | 夕颜

大多数人都认为异步Python的并发程度更高。这意味着对于动态网站或Web API等常见任务,异步能提供更高的性能。

但遗憾的是,对于Python解释器来说,异步并不能提供加速的推力。

在现实情境下(请参考下图),异步Web框架的吞吐量(请求/秒)表现更差一些,而响应延迟则差的更多。

基准结果

我测试了各种不同的同步和异步Web服务器配置:

P50和P99的响应时间以毫秒为单位,Throughput(吞吐量)以每秒请求量为单位。这个表格按P99排序,我认为这可能是现实世界中最重要的统计信息。

要注意以下几点:

1、表现最好的几个都是同步框架

但Flask的吞吐量比其他的要低

2、表现最差的几个都是异步框架

3、异步框架的响应延迟方面也差得多

4、基于uvloop的循环比内置的asyncio循环更好

因此,如果非要用asyncio,请选择uvloop

这些基准测试真的具有代表性吗?

我认为的确如此。我尽力使它们贴近现实情况了,使用的架构是这个:

我尽可能地模拟真实世界的部署:一个反向代理,python代码(比如变量),后面再跟着一个数据库。我还加入了一个外部数据库连接池,因为我认为在Web应用程序实际部署中这种做法相当常见(至少对postgresql来说是这样的)。

测试的应用程序通过随机key查询数据库某行,并将值以JSON的形式返回。完整的源代码可在github上找到。

(链接:https://github.com/calpaterson/python-web-perf)

为什么工作进程数(worker count)不一样?

我用来确定最佳工作进程数的原则十分简单:对于每个框架, 我都是从一个开始,连续增加数量,直到性能变差。

异步和同步框架之间的最佳工作程序数量有所不同,其中的原因也显而易见。异步框架由于其IO并发性,单个工作进程就能让一个CPU饱和。

而同步工作进程的情况就不一样了:当他们执行IO时会进入阻塞,直到IO完成。因此,他们需要有足够的工作进程,来确保在负载下所有CPU核心始终处于满负荷状态。

更多这方面的信息,请参见gunicorn文档:

通常,我们建议(2 x $ num_cores)+ 1作为启示的worker数量。尽管不是太科学,但这个公式是基于这个假设的:对于一个给定的内核,当一个worker在处理请求时,另外一个 worker可以从套接字中读写数据。

(文档链接:https://docs.gunicorn.org/en/stable/design.html#how-many-workers)

机器规格

我在Hetzner的CX31机器类型上运行了基准测试,它是一个4 vCPU / 8 GB内存的机器,运行在Ubuntu 20.04上。在另一个(较小的)虚拟机上运行了施压程序。

为什么异步表现更糟糕?

吞吐量

对于吞吐量(即:请求/秒),主要影响因素不是异步和同步,而是用本地代码替换了多少Python代码。简而言之,你能替换的对性能敏感的Python代码越多,性能就越好。这是历史悠久的Python性能策略(另请参考numpy)。

Meinheld和UWSGI(每个约5.3k请求量/秒)包含了大量C语言代码。而标准 Gunicorn(约 3.4k请求量/秒)是纯 Python。

Uvicorn + Starlette(~4.9k请求/秒)比 AIOHTTP 的默认服务器(~4.5k请求量/秒)替换了更多的 Python 代码(尽管 AIOHTTP 也安装了它的可选 “加速”)。

延迟

在延迟方面,问题则是更深层次的。与传统的同步部署相比,在负载下,异步的表现很差,并且延迟开始急剧上升。

为什么会出现这种情况呢?在异步Python中,多线程合作式(co-operative)的,简单来说意思就是线程不会被中央控制器(例如内核)打断,而必须主动把执行时间分配给其他人。在asyncio中,执行取决于三个语言关键字:await,async for和async with。

这意味着执行时间不是“公平”分配的,并且一个线程在工作时可能会无意间让另一个线程得不到CPU时间而饿死。这就是延迟较为不稳定的原因。

相形之下,诸如UWSGI之类的传统同步Python Web服务器,使用的是内核调度程序的抢占式(pre-emptive)多进程,其工作原理是通过定期从执行中换出进程来确保公平性。这意味着时间分配更加公平,并且延迟差异也较小。

为什么其他基准测试显示的结果不同?

大多数其他基准测试(尤其是那些来自异步框架作者的!)根本没有为同步框架配置足够的工作进程数。这意味着,大部分实际上可用的CPU时间这些同步框架根本无法接触到。

这是Vibora项目的基准示例。(我没有测试这个框架,因为它不是特别流行。)

Vibora声称在吞吐量上要比Flask高500%。但是,当我查看他们的基准代码时,发现他们误将Flask配置为每个CPU只使用一个工作进程。更正后,我得到了以下结果:

在吞吐量方面,使用Vibora只比使用Flask提升了18%。Flask是我测试过的吞吐量较低的同步框架之一,因此尽管上面Vibora自己给出的图表看起来很牛,但我觉得会有更好的同步设置比Vibora快得多。

另一个问题是,许多基准测试降低了延迟的优先级,而偏向吞吐量的结果(例如,Vibora甚至没有提及延迟)。然而,吞吐量可以通过增加机器来优化,但负载下的延迟却不能。

只有在延迟处于可接受范围内时,增加吞吐量才真的有意义。

进一步的推理,假设和传闻

虽然基准测试在设计方面尽量接近现实,但也仍然比现实生活中的工作负载要单调得多—— 所有的请求都会做一个数据库查询,都会用这个查询做同样的事情。真实的应用场景通常会有更丰富的变化,会有一些慢的,也会有一些快的操作,一些请求做了很多 IO,另外一些使用了很多 CPU。似乎有理由假设(根据我的经验也是如此),在真实的应用中,延迟变化实际上要高得多。

我的直觉是,在真实场景下,异步应用程序的性能会出现更多的问题。公开的传闻与我这个想法一致:

Dan McKinley曾经分享过他在Etsy管理一个基于Twisted系统的经历。似乎那个系统遭受了延迟变大的困扰:

[Twisted的顾问]说,尽管Twisted在整体吞吐量方面表现很好,但比较偏的请求可能会遭遇严重的延迟。这对于[Etsy的系统]来说是个问题,因为PHP前端使用它的方式是每个Web请求访问上百/上千次。

SQLAlchemy的作者Mike Bayer几年前写了《异步Python和数据库》,其中他从一个稍微不同的角度考虑了异步的问题。他还进行了基准测试,发现asyncio效率较低。

(链接:https://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/)

Rachel by the Bay撰写了一篇题为“我们必须谈论Python,Gunicorn,Gevent”的文章,其中她描述了基于gevent配置所产生的操作混乱。我在生产中使用gevent时也曾经遇到过麻烦(尽管与性能无关)。

(链接:https://rachelbythebay.com/w/2020/03/07/costly/)

我还要提到的另一件事是,在设置这些基准测试的过程中,每个异步实现最终都以一种烦人的方式挂了。

Uvicorn的父进程在不终止其子进程的情况下就退出了,这意味着我不得不去寻找那些仍然留在端口8001上的子进程。有那么一次,AIOHTTP提出了一个内部严重报错,这个报错与文件描述符有关,但它并没有退出(因此任何进程监控脚本都不会重启它——这简直是犯了重大错误!)。Daphne在本地也遇到了麻烦,但我忘了具体是怎么回事了。

所有这些错误都是暂时性的,可以使用SIGKILL轻松解决。但是事实仍然是,我不想在生产环境中负责基于这些库的代码。相比之下,我在用Gunicorn或UWSGI时就没有出现过任何问题——除了一点,我真的不喜欢UWSGI 在应用没有正确加载时不会退出这个特性。

结论

我的建议:出于性能方面的考虑,仅使用普通的同步Python即可,但尽可能使用native代码。对于Web服务器,如果吞吐量至关重要,那么值得考虑一下使用Flask以外的框架,但即使是UWSGI下的Flask也具有最佳延迟特性。

感谢我的朋友Tudor Munteanu帮我仔细检查了文中的数据。

参考

Flask的原始作者已经发表了几篇文章,表达了他对异步的担忧,首先是《我不理解Python的Asyncio》,这篇对异步技术做了很好的解释,最近又发表了《我没有感受到异步的压力》一文,里面说:

async / await很棒,但是它鼓励大家写的东西会在过载时出现灾难性的结果

(链接:https://lucumr.pocoo.org/2016/10/30/i-dont-understand-asyncio/

https://lucumr.pocoo.org/2020/1/1/async-pressure/)

《你的函数是什么颜色?》一文中解释了为什么一个语言如果同时有同步和异步,会使开发过程更加痛苦的一些原因。

(链接:https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)

函数着色是Python中的一个大问题,可悲的是,现在的社区被划分为写同步代码的人和写异步代码的人——他们不能共享相同的库。更糟糕的是,某些异步库还不能与其他异步库兼容,所以异步Python社区甚至更加分裂了。

Chris Wellons最近写了一篇文章,文中还谈到了延迟问题和asyncio标准库中的一些注脚。遗憾的是,这种问题恰恰能使异步程序更加难以处理。

(链接:https://nullprogram.com/blog/2020/05/24/)

另外,Nathaniel J.Smith认为异步库的概念是错误的。我的担忧是,如果辩论PEP规范的那些大牛们都搞不清,那么像我这样的普通开发者还有希望吗?

作者简介:

Cal Paterson,独立软件工程师,主要工作语言为Python,对数据库和通讯方面的后端问题兴趣颇深,现居伦敦。

原文链接:

http://calpaterson.com/async-python-is-not-faster.html

本文为CSDN翻译文章,转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值