但遗憾的是,async 对付 Python 阐明器来说,并不是一个加速条。
在现实条件下的数据(见下图),异步网络框架的吞吐量(要求量/秒)更差,相应延迟也大得多。
基准结果

我测试了各种不同的同步和异步的 Web 做事器配置。
第 50 和 99 分位数的相应韶光单位是毫秒, 吞吐量单位是每秒要求量。该表按 P99 排序,我认为这可能是现实天下中最主要的统计指标。
一些把稳事变:
表现最好的是同步框架
但 Flask 的吞吐量比其他的要低
表现差的全都是异步框架
异步框架的相应延迟也差很多
基于 Uvloop 的循环比内置的 asyncio 循环做得更好。
如果不得不该用 asyncio,请选择 uvloop。
这些基准测试有代表性吗?
我认为如此,我只管即便让基准运行的场景贴近真实,下面是利用的架构。
我尽可能地仿照真实天下的支配:一个反向代理,中间是 Python 代码,后面一个数据库。我还利用了数据库连接池,这是真实的 Web 运用支配中常见的做法(至少对付 postgresql 来说是这样)。
测试的运用程序通过随机 key 查询数据库某一行,并以 JSON 形式返回。完全的源代码可以参看 github :
https://github.com/calpaterson/python-web-perf
为什么事情进程 worker 数设置不一样?
决定最佳 worker 数量是多少的规则很大略:对付每个框架,我从 1 个 worker 开始,连续增加数量,直到性能变差。
Async 和 sync 框架的最佳 worker 数量在有所不同,缘故原由很大略,async 框架由于其 IO 并发性,一个 worker 进程就能让一个 CPU 跑满。
而同步 worker 就不一样了,它们做 IO 时会调用壅塞,直到 IO 完成。因此,它们须要有更多的 worker,以确保在负载时所有 CPU 核心始终处于满负荷状态。
关于这方面的更多信息,请拜会 gunicorn 文档。
一样平常来说,我们建议 (2 x $num_cores) + 1 作为开始的 worker 数量。虽然这个公式并不太科学,但它是基于这样的假设:对付一个给定的 core,当一个 worker 在处理要求时,其余一个 worker 可以从套接字中读写数据。
机器规格
我在 Hetzner 的 CX31 机器类型上运行了基准测试,它是一个4 vCPU / 8 GB 内存的机器,运行在 Ubuntu 20.04 上。在另一个(较小的)虚拟机上运行了施压程序。
为什么 async 表现更差?
吞吐量
吞吐量(即:要求量/秒)最紧张的成分不是 async 还是 sync,而是有多少 Python 代码被更换成了本地代码。大略的说,你能更换的对性能敏感的 Python 代码越多,性能就越好。这是 Python 性能战术,历史悠久(另见:numpy)。
Meinheld 和 UWSGI(每个约 5.3k要求量/秒)包含了大量的 C 代码。标准 Gunicorn(约 3.4k要求量/秒)基于纯 Python。
Uvicorn + Starlette(~4.9k要求/秒)比 AIOHTTP 的默认做事器(~4.5k要求量/秒)更换了更多的 Python 代码(只管 AIOHTTP 也安装了它的可选 \"大众加速\公众)。
延时
在相应延迟上,问题更繁芜。在要求负载下,async 的表现很糟糕,延迟开始飙升,比传统的同步支配,延迟的程度要大得多。
为什么会这样呢?在 async Python 中,多线程是互助式(co-operative)的,大略来说便是线程不被中心管理者(比如内核)打断,而是要主动把实行韶光让给别人。在 asyncio 中,实行韶光是在三个措辞关键词上转让的:await、async for 和 async with。
这意味着实行韶光并不是 \"大众公正 \"大众分配的,一个线程在事情时可能会无意中饿去世另一个线程的 CPU 韶光。这便是为什么延迟比较不稳定的缘故原由。
比较之下,传统的同步 Python webservers,比如 UWSGI,利用的是内核调度器的抢占式(Pre-emptive)的多进程,它的事情事理是通过周期性地将进程从实行中交流出来,以担保公正性。这意味着韶光的分配更加公正,延迟差异更低。
为什么其他基准显示的结果不同?
大多数其他基准(尤其是那些来自 async 框架作者的基准)根本没有为同步框架配置足够的 worker。这意味着,这些同步框架实际上无法合理利用真正可用的大部分 CPU 韶光。
下面是 Vibora 项目的一个样本基准(我没有测试这个框架,由于它是一个不太盛行的框架)。
Vibora 声称比 Flask 赶过 500% 的吞吐量。然而,当我审查他们的基准代码时,创造他们缺点地将 Flask 配置为每个 CPU 利用一个 worker。当我纠正这个问题时,得到了以下结果。
利用 Vibora 比 Flask 的吞吐量上风实在只有 18%。Flask 是我测试过的吞吐量较低的同步框架之一,以是我认为一个更好的同步设置会比 Vibora 快得多,只管这个图看起来令人印象深刻。
另一个问题是,许多基准都会去掉相应延迟的统计数据,而方向于吞吐量结果(例如 Vibora 的基准乃至没有提到它)。然而,增加吞吐量实在可以通过大略增加机器来提高,但在高负载下的延迟不佳的话并没有直接的办理办法。
只有在延迟在可接管的范围内,提高吞吐量才真正故意义。
进一步的推理、假设和传闻
虽然基准测试在设计方面只管即便靠近现实,但它仍旧比现实生活中的事情负载要单调得多 —— 所有的要求都会做一个数据库查询,都会用这个查询做同样的事情。真实的运用常日会有更丰富的变革:会有一些慢的以及快的操作,一些要求做了很多 IO,其余一些利用了很多 CPU。彷佛有情由假设(根据我的履历也是如此),在真实的运用中,延迟变革实际上要高得多。
在这种情形下,我的预感 async 运用的性能会更有问题。公开的传闻与这个想法同等。
Dan McKinley 分享了他在 Etsy 管理一个基于 Twisted 系统的经历。彷佛那个系统受到了延迟变大的困扰。
[Twisted的顾问]说,虽然 Twisted 在整体吞吐量上很好,但冷僻的访问要求可能会涌现严重的延迟,这对 [Etsy的系统] 来说是个问题,由于 PHP 前真个利用办法是每个 web 要求都会访问几百或几千次。
SQLAlchemy 的作者 Mike Bayer 在几年前写了《异步 Python 和数据库》(1),他在书中从一个轻微不同的角度考虑了异步的问题。他还进行了基准测试,创造 asyncio 的效率较低。
https://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/
Rachel by the Bay 写了一篇文章《我们必须谈谈 Python、Gunicorn、Gevent 这件事》(1),文章中描述了基于 gevent 配置所产生的操作混乱。我也曾在生产中碰着过 gevent 的麻烦(虽然与性能无关)。
https://rachelbythebay.com/w/2020/03/07/costly/
我还须要提到的一件事是,在设置这些基准的过程中,每一个 async 实现都终极以一种令人讨厌的办法挂掉。
Uvicorn 的父进程在没有终止任何子进程的情形下就退出了,这意味着我不得不去探求那些还在 8001 端口的子进程。有一次,AIOHTTP 抛出了一个与文件描述符有关的内部严重缺点,但它并没有退出 (因此任何进程监控脚本都不会重新启动它 —— 这可是大罪!
)。Daphne 也在本地碰着了麻烦,但我忘了详细是怎么碰着的。
所有这些缺点都是短暂的,用 SIGKILL 很随意马虎办理。但实际我不想在生产环境中卖力基于这些库的代码。比较之下,我在利用 Gunicorn 或 UWSGI 时没有碰着任何问题 —— 除了UWSGI 在运用没有精确加载时不会退出。
总结
我的建议是:出于性能的考虑,利用普通的、同步的 Python 即可,但只管即便利用 native 代码。对付 webserver 来说,如果吞吐量是最主要的,值得考虑 Flask 以外的框架,但纵然是 UWSGI 下的 Flask, 也有最好的延迟特性。
感谢 Tudor Munteanu 帮忙检讨了文章中的数据。
参阅
Flask 作者已经写过几篇文章,表达了他对 async 的担忧,第一篇是《我不理解 Python 的 asyncio》(1),对 async 技能做了非常好的阐明,最近又发了《我觉得不到 async 的压力》(2),里面提到
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 中的一个大问题,现在社区很悲哀地分成了写同步代码的人和写 async 代码的人 —— 他们不能共享同一个库。更糟糕的是,一些异步库还与其余一些异步库不兼容,以是异步 Python 社区更加分裂。
Chris Wellons 最近写了一篇文章,个中也提到了延迟问题和 asyncio 标准库中的一些注脚。不幸的是,这是一种让异步程序更难搞好的问题。
https://program.com/blog/2020/05/24/
Notes on structured concurrency https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
Some thoughts on asynchronous API design in a post-async/await world https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/
Control-C handling in Python and Trio https://vorpus.org/blog/control-c-handling-in-python-and-trio/
Timeouts and cancellation for humans https://vorpus.org/blog/timeouts-and-cancellation-for-humans/
他认为 asyncio 库的观点是缺点的。我担心的是,如果谈论 PEPs 规范的那些前辈们都搞不清楚,像我这样的普通开拓者就更没戏了。
英文原文:
http://calpaterson.com/async-python-is-not-faster.html
本文由高可用架构翻译,技能原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。
高可用架构
改变互联网的构建办法
大多数人都知道 async Python 具有更高的并发性。这意味着对付常见的任务如动态网站或 Web API, async 性能更好。
但遗憾的是,async 对付 Python 阐明器来说,并不是一个加速条。
在现实条件下的数据(见下图),异步网络框架的吞吐量(要求量/秒)更差,相应延迟也大得多。
基准结果
我测试了各种不同的同步和异步的 Web 做事器配置。
第 50 和 99 分位数的相应韶光单位是毫秒, 吞吐量单位是每秒要求量。该表按 P99 排序,我认为这可能是现实天下中最主要的统计指标。
一些把稳事变:
表现最好的是同步框架
但 Flask 的吞吐量比其他的要低
表现差的全都是异步框架
异步框架的相应延迟也差很多
基于 Uvloop 的循环比内置的 asyncio 循环做得更好。
如果不得不该用 asyncio,请选择 uvloop。
这些基准测试有代表性吗?
我认为如此,我只管即便让基准运行的场景贴近真实,下面是利用的架构。
我尽可能地仿照真实天下的支配:一个反向代理,中间是 Python 代码,后面一个数据库。我还利用了数据库连接池,这是真实的 Web 运用支配中常见的做法(至少对付 postgresql 来说是这样)。
测试的运用程序通过随机 key 查询数据库某一行,并以 JSON 形式返回。完全的源代码可以参看 github :
https://github.com/calpaterson/python-web-perf
为什么事情进程 worker 数设置不一样?
决定最佳 worker 数量是多少的规则很大略:对付每个框架,我从 1 个 worker 开始,连续增加数量,直到性能变差。
Async 和 sync 框架的最佳 worker 数量在有所不同,缘故原由很大略,async 框架由于其 IO 并发性,一个 worker 进程就能让一个 CPU 跑满。
而同步 worker 就不一样了,它们做 IO 时会调用壅塞,直到 IO 完成。因此,它们须要有更多的 worker,以确保在负载时所有 CPU 核心始终处于满负荷状态。
关于这方面的更多信息,请拜会 gunicorn 文档。
一样平常来说,我们建议 (2 x $num_cores) + 1 作为开始的 worker 数量。虽然这个公式并不太科学,但它是基于这样的假设:对付一个给定的 core,当一个 worker 在处理要求时,其余一个 worker 可以从套接字中读写数据。
机器规格
我在 Hetzner 的 CX31 机器类型上运行了基准测试,它是一个4 vCPU / 8 GB 内存的机器,运行在 Ubuntu 20.04 上。在另一个(较小的)虚拟机上运行了施压程序。
为什么 async 表现更差?
吞吐量
吞吐量(即:要求量/秒)最紧张的成分不是 async 还是 sync,而是有多少 Python 代码被更换成了本地代码。大略的说,你能更换的对性能敏感的 Python 代码越多,性能就越好。这是 Python 性能战术,历史悠久(另见:numpy)。
Meinheld 和 UWSGI(每个约 5.3k要求量/秒)包含了大量的 C 代码。标准 Gunicorn(约 3.4k要求量/秒)基于纯 Python。
Uvicorn + Starlette(~4.9k要求/秒)比 AIOHTTP 的默认做事器(~4.5k要求量/秒)更换了更多的 Python 代码(只管 AIOHTTP 也安装了它的可选 \公众加速\公众)。
延时
在相应延迟上,问题更繁芜。在要求负载下,async 的表现很糟糕,延迟开始飙升,比传统的同步支配,延迟的程度要大得多。
为什么会这样呢?在 async Python 中,多线程是互助式(co-operative)的,大略来说便是线程不被中心管理者(比如内核)打断,而是要主动把实行韶光让给别人。在 asyncio 中,实行韶光是在三个措辞关键词上转让的:await、async for 和 async with。
这意味着实行韶光并不是 \公众公正 \"大众分配的,一个线程在事情时可能会无意中饿去世另一个线程的 CPU 韶光。这便是为什么延迟比较不稳定的缘故原由。
比较之下,传统的同步 Python webservers,比如 UWSGI,利用的是内核调度器的抢占式(Pre-emptive)的多进程,它的事情事理是通过周期性地将进程从实行中交流出来,以担保公正性。这意味着韶光的分配更加公正,延迟差异更低。
为什么其他基准显示的结果不同?
大多数其他基准(尤其是那些来自 async 框架作者的基准)根本没有为同步框架配置足够的 worker。这意味着,这些同步框架实际上无法合理利用真正可用的大部分 CPU 韶光。
下面是 Vibora 项目的一个样本基准(我没有测试这个框架,由于它是一个不太盛行的框架)。
Vibora 声称比 Flask 赶过 500% 的吞吐量。然而,当我审查他们的基准代码时,创造他们缺点地将 Flask 配置为每个 CPU 利用一个 worker。当我纠正这个问题时,得到了以下结果。
利用 Vibora 比 Flask 的吞吐量上风实在只有 18%。Flask 是我测试过的吞吐量较低的同步框架之一,以是我认为一个更好的同步设置会比 Vibora 快得多,只管这个图看起来令人印象深刻。
另一个问题是,许多基准都会去掉相应延迟的统计数据,而方向于吞吐量结果(例如 Vibora 的基准乃至没有提到它)。然而,增加吞吐量实在可以通过大略增加机器来提高,但在高负载下的延迟不佳的话并没有直接的办理办法。
只有在延迟在可接管的范围内,提高吞吐量才真正故意义。
进一步的推理、假设和传闻
虽然基准测试在设计方面只管即便靠近现实,但它仍旧比现实生活中的事情负载要单调得多 —— 所有的要求都会做一个数据库查询,都会用这个查询做同样的事情。真实的运用常日会有更丰富的变革:会有一些慢的以及快的操作,一些要求做了很多 IO,其余一些利用了很多 CPU。彷佛有情由假设(根据我的履历也是如此),在真实的运用中,延迟变革实际上要高得多。
在这种情形下,我的预感 async 运用的性能会更有问题。公开的传闻与这个想法同等。
Dan McKinley 分享了他在 Etsy 管理一个基于 Twisted 系统的经历。彷佛那个系统受到了延迟变大的困扰。
[Twisted的顾问]说,虽然 Twisted 在整体吞吐量上很好,但冷僻的访问要求可能会涌现严重的延迟,这对 [Etsy的系统] 来说是个问题,由于 PHP 前真个利用办法是每个 web 要求都会访问几百或几千次。
SQLAlchemy 的作者 Mike Bayer 在几年前写了《异步 Python 和数据库》(1),他在书中从一个轻微不同的角度考虑了异步的问题。他还进行了基准测试,创造 asyncio 的效率较低。
https://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/
Rachel by the Bay 写了一篇文章《我们必须谈谈 Python、Gunicorn、Gevent 这件事》(1),文章中描述了基于 gevent 配置所产生的操作混乱。我也曾在生产中碰着过 gevent 的麻烦(虽然与性能无关)。
https://rachelbythebay.com/w/2020/03/07/costly/
我还须要提到的一件事是,在设置这些基准的过程中,每一个 async 实现都终极以一种令人讨厌的办法挂掉。
Uvicorn 的父进程在没有终止任何子进程的情形下就退出了,这意味着我不得不去探求那些还在 8001 端口的子进程。有一次,AIOHTTP 抛出了一个与文件描述符有关的内部严重缺点,但它并没有退出 (因此任何进程监控脚本都不会重新启动它 —— 这可是大罪!
)。Daphne 也在本地碰着了麻烦,但我忘了详细是怎么碰着的。所有这些缺点都是短暂的,用 SIGKILL 很随意马虎办理。但实际我不想在生产环境中卖力基于这些库的代码。比较之下,我在利用 Gunicorn 或 UWSGI 时没有碰着任何问题 —— 除了UWSGI 在运用没有精确加载时不会退出。
总结
我的建议是:出于性能的考虑,利用普通的、同步的 Python 即可,但只管即便利用 native 代码。对付 webserver 来说,如果吞吐量是最主要的,值得考虑 Flask 以外的框架,但纵然是 UWSGI 下的 Flask, 也有最好的延迟特性。
感谢 Tudor Munteanu 帮忙检讨了文章中的数据。
参阅
Flask 作者已经写过几篇文章,表达了他对 async 的担忧,第一篇是《我不理解 Python 的 asyncio》(1),对 async 技能做了非常好的阐明,最近又发了《我觉得不到 async 的压力》(2),里面提到
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 中的一个大问题,现在社区很悲哀地分成了写同步代码的人和写 async 代码的人 —— 他们不能共享同一个库。更糟糕的是,一些异步库还与其余一些异步库不兼容,以是异步 Python 社区更加分裂。
Chris Wellons 最近写了一篇文章,个中也提到了延迟问题和 asyncio 标准库中的一些注脚。不幸的是,这是一种让异步程序更难搞好的问题。
https://program.com/blog/2020/05/24/
Nathaniel J. Smith 有一系列关于 async 的精彩文章,推举给有兴趣的读者。
Notes on structured concurrency https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
Some thoughts on asynchronous API design in a post-async/await world https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/
Control-C handling in Python and Trio https://vorpus.org/blog/control-c-handling-in-python-and-trio/
Timeouts and cancellation for humans https://vorpus.org/blog/timeouts-and-cancellation-for-humans/
他认为 asyncio 库的观点是缺点的。我担心的是,如果谈论 PEPs 规范的那些前辈们都搞不清楚,像我这样的普通开拓者就更没戏了。
英文原文:
http://calpaterson.com/async-python-is-not-faster.html
参考阅读: 谈谈PHP8新特性Attributes 如何做好Code Review? 分享一份我们团队的 Checklist 分布式算法 Paxos 的直不雅观阐明 (TL;DR) 重构,还是重写?(2020版) 深入浅出Rust异步编程之Tokio 深入理解同步/异步与壅塞/非壅塞差异 本文由高可用架构翻译,技能原创及架构实践文章,欢迎通过"大众号菜单「联系我们」进行投稿。 高可用架构 改变互联网的构建办法 长按二维码 关注「高可用架构」"大众号