← 返回题目列表

uvloop 是什么?换事件循环真的能让异步程序变快吗?

中等 第 26 / 27 题 更新于 2026/08/01
uvloop事件循环性能优化libuv

简化版

asyncio 的事件循环是可替换的**——标准库提供的只是默认实现,你可以换成第三方的高性能实现。最著名的就是 uvloop:它用 Cython 封装了 libuv(Node.js 底层用的那个 C 库),替换掉 asyncio 纯 Python 实现的事件循环和网络传输层性能提升的来源有三处:① 事件循环的核心调度用 C 实现(回调分发、定时器堆、IO 多路复用都不再走 Python 字节码);② Transport 层用 libuv 直接处理 socket 读写,避免了 Python 层的多次函数调用和内存拷贝;③ 更高效的缓冲区管理实测提升通常在 2~4 倍(官方基准宣称最高 2~4 倍,接近 Go 的网络性能),但这个数字只在「网络 IO 密集且每个请求处理很轻」时才成立——如果你的瓶颈在数据库、下游 API 或业务的 CPU 计算上,换 uvloop 几乎没有任何提升(因为事件循环本来就不是瓶颈)。用法极简:3.12+ 推荐 asyncio.run(main(), loop_factory=uvloop.new_event_loop);老写法 uvloop.install() 已被弃用。uvicorn/gunicorn 会自动检测并使用它最大的限制是不支持 Windows(libuv 的 IOCP 封装与 asyncio 的 Proactor 模型不兼容,可以用 winloop 这个替代品)。核心记忆:uvloop = Cython + libuv 重写事件循环和传输层只对「网络 IO 密集」有效,业务瓶颈在别处时换了也白换;不支持 Windows

详细版

几种事件循环实现

实现平台说明
SelectorEventLoopUnix/WindowsUnix 默认,基于 epoll/kqueue
ProactorEventLoop仅 WindowsWindows 默认(3.8+),基于 IOCP,支持子进程
uvloopLinux/macOS基于 libuv 的 Cython 实现,快 2~4 倍
winloopWindowsuvloop 的 Windows 移植
rloop(实验)LinuxRust 实现,较新
import asyncio, sys

# ① ★3.12+ 推荐写法:loop_factory★
import uvloop
asyncio.run(main(), loop_factory=uvloop.new_event_loop)

# ② 老写法(★uvloop 0.21+ 已弃用 install()★)
# uvloop.install()                       # ★DeprecationWarning★
# asyncio.run(main())

# ③ 手动创建(需要复用 loop 时)
loop = uvloop.new_event_loop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.close()

# ④ ★优雅降级(Windows 上自动回退)★
def run(coro):
    try:
        import uvloop
        return asyncio.run(coro, loop_factory=uvloop.new_event_loop)
    except ImportError:
        return asyncio.run(coro)          # ★Windows / 未安装时用标准实现★

# ⑤ 确认当前用的是哪个循环
loop = asyncio.get_running_loop()
print(type(loop))          # <class 'uvloop.Loop'> 或 <class 'asyncio.unix_events._UnixSelectorEventLoop'>

# ⑥ 框架集成(★通常不用自己写★)
# uvicorn: --loop uvloop(★默认 auto,检测到就用★)
#   uvicorn app:app --loop uvloop --workers 4
# gunicorn + uvicorn worker:
#   gunicorn app:app -k uvicorn.workers.UvicornWorker
# aiohttp: web.run_app(app, loop=uvloop.new_event_loop())

# ⑦ ★Windows 的选择★
if sys.platform == "win32":
    # uvloop 不支持 Windows
    import winloop                        # ★uvloop 的 Windows 移植★
    asyncio.run(main(), loop_factory=winloop.new_event_loop)

⚠️ 三个必须澄清的认知:① uvloop 加速的是「事件循环本身和网络传输层」,不是你的业务代码。它把「回调分发、定时器管理、socket 读写」这些框架开销从 Python 层挪到了 C 层,所以在「每秒处理几万个小请求、每个请求只是转发或返回缓存」这类场景下提升明显(2~4 倍);但如果你的请求要查一次数据库(10ms)、调一次下游 API(50ms),事件循环的那点开销(微秒级)根本不是瓶颈,换了等于没换。② uvloop.install() 已被弃用(0.21 起)——它通过设置全局的 EventLoopPolicy 生效,而 Python 3.12 起 EventLoopPolicy 整体被标记为弃用、3.16 计划移除;新写法是 asyncio.run(main(), loop_factory=uvloop.new_event_loop)(3.12+ 的 asyncio.run 支持 loop_factory 参数)。③ uvloop 不支持 Windows——libuv 在 Windows 上用的是 IOCP,与 asyncio 的 Proactor 模型难以对接。跨平台项目要么用 winloop(uvloop 的 Windows 移植),要么写优雅降级的代码(try: import uvloop except ImportError: 用标准实现)。

完整版教学

一、事件循环是可替换的

★ asyncio 的分层设计(★这是能替换的前提★):
  ┌────────────────────────────────────────────┐
  │ 你的代码:async/await、Task、Future         │  ← ★不变★
  ├────────────────────────────────────────────┤
  │ ★AbstractEventLoop 接口★                   │  ← ★契约★
  │   run_until_complete / create_task /        │
  │   sock_recv / create_connection / ...       │
  ├────────────────────────────────────────────┤
  │ 具体实现:SelectorEventLoop / uvloop.Loop   │  ← ★可替换★
  ├────────────────────────────────────────────┤
  │ 系统调用:epoll / kqueue / IOCP             │
  └────────────────────────────────────────────┘
  → 只要实现了 AbstractEventLoop 的接口,就能被 asyncio 用
  → ★你的 async/await 代码一行都不用改★

★ 标准库的两个实现:
  ★SelectorEventLoop★
    - 基于 selectors 模块(自动选 epoll/kqueue/select)
    - ★Unix 默认★;Windows 上也能用但★不支持子进程和某些特性★
  ★ProactorEventLoop★
    - 基于 Windows 的 ★IOCP★(完成端口)
    - ★Windows 默认(3.8+)★,支持子进程、管道
    - ★仅 Windows★

★ 怎么切换(★三种方式,注意版本★):
  ① ★loop_factory(3.12+,推荐)★
     asyncio.run(main(), loop_factory=uvloop.new_event_loop)
  ② 手动创建 + set_event_loop
     loop = uvloop.new_event_loop(); asyncio.set_event_loop(loop)
  ③ ★EventLoopPolicy(老方式,正在被弃用)★
     asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
     ★3.12 起 policy 相关 API 被标记弃用,3.16 计划移除★
     → uvloop.install() 内部就是它 → ★同样被弃用★

★ 为什么标准实现"慢":
  纯 Python 实现意味着每一次:
    - 从 epoll 拿到就绪事件 → ★Python 层遍历、查字典找回调★
    - 调用回调 → ★Python 函数调用开销★
    - socket 读写 → ★Python 层的 buffer 管理和多次内存拷贝★
    - 定时器 → ★heapq(Python 实现)★
  → 单次开销是微秒级,但★每秒几十万次就很可观★
  → uvloop 把这些全挪到 C 层

理解 uvloop 的前提是 asyncio 的分层设计:你的 async/await 代码、TaskFuture 都建立在 AbstractEventLoop 这个抽象接口之上,而具体实现是可替换的——只要实现了接口契约,就能被 asyncio 使用,你的业务代码一行都不用改。标准库有两个实现:SelectorEventLoop(基于 epoll/kqueue,Unix 默认)和 ProactorEventLoop(基于 Windows 的 IOCP,Windows 默认且支持子进程)。切换方式随版本演进:3.12+ 推荐 asyncio.run(main(), loop_factory=...),而老的 EventLoopPolicy 体系(包括 uvloop.install())已被标记弃用、3.16 计划移除。标准实现「慢」的原因很具体:每次从 epoll 拿到事件都要在 Python 层遍历查字典找回调、每次 socket 读写都有 Python 层的缓冲管理和多次内存拷贝、定时器用的是 Python 实现的 heapq——单次开销是微秒级,但每秒几十万次就很可观。

二、uvloop 快在哪

★ uvloop = Cython 封装的 libuv
  libuv:Node.js 的底层库,用 C 写的跨平台异步 IO 库
  Cython:把 Python 语法编译成 C 扩展
  → uvloop 用 Cython 实现了 asyncio 的 EventLoop 和 Transport 接口,
    底层调用 libuv

★ 三处提速:
  ① ★事件循环核心★
     - 就绪事件的分发、回调队列、定时器堆 → ★全在 C 层★
     - 不需要每次都进出 Python 解释器
  ② ★Transport / Protocol 层(★提升最大的地方★)★
     标准实现:epoll 就绪 → Python 回调 → sock.recv() → Python bytes →
               调 protocol.data_received() → 又是 Python 调用
     uvloop:  libuv 直接读进缓冲区 → ★一次性交给 Python★
     → 减少了大量 Python 函数调用和中间对象
  ③ ★内存管理★
     - 复用缓冲区,减少 bytes 对象的创建和 GC 压力

★ 官方基准(★要看清测的是什么★):
  uvloop 的 README 宣称:
    - 比标准 asyncio ★快 2~4 倍★
    - "在很多场景下接近甚至超过 Go 的性能"
  ★ 但基准测的是:★纯 TCP echo / HTTP 静态响应★
    → 这类场景里"事件循环 + 传输层"就是全部工作量
    → ★真实业务里它只占很小一部分★

★ 真实场景的提升(★经验值★):
  ┌────────────────────────────────────┬──────────────┐
  │ 场景                                │ 提升          │
  ├────────────────────────────────────┼──────────────┤
  │ TCP echo / 静态响应(纯框架开销)     │ ★2~4 倍★     │
  │ 简单 HTTP API(返回小 JSON、无 IO)  │ ★1.3~2 倍★   │
  │ API + 一次数据库查询(10ms)         │ ★< 10%★      │
  │ API + 调用下游服务(50ms+)          │ ★几乎无提升★ │
  │ CPU 密集的业务逻辑                   │ ★0(可能更慢)★│
  └────────────────────────────────────┴──────────────┘
  ★ 结论:★事件循环的开销占比越高,uvloop 的收益越大★
    → 先 profile,确认瓶颈真的在框架层再换

★ 一个直观的判断:
  你的单个请求处理耗时是多少?
    < 100 微秒   → ★框架开销占大头,uvloop 收益明显★
    1~10 毫秒    → 有一些收益
    > 10 毫秒    → ★瓶颈在别处,换了基本没用★

uvloop 是用 Cython 封装的 libuv(Node.js 底层的那个 C 库),它实现了 asyncio 的 EventLoopTransport 接口。三处提速事件循环核心(就绪事件分发、回调队列、定时器堆全在 C 层,不用每次进出 Python 解释器)、Transport/Protocol 层(提升最大的地方)(libuv 直接把数据读进缓冲区一次性交给 Python,省掉了标准实现里大量的 Python 函数调用和中间对象)、以及更高效的缓冲区复用。官方宣称的「快 2~4 倍」要看清基准测的是什么——测的是纯 TCP echo 和静态 HTTP 响应,在那种场景里「事件循环 + 传输层」就是全部工作量。真实业务里的经验值是:纯框架开销场景 24 倍、简单 JSON API 1.32 倍、带一次数据库查询不到 10%、调用下游服务几乎无提升、CPU 密集为 0。一个直观的判断标准:单个请求处理耗时小于 100 微秒时收益明显,超过 10 毫秒基本没用——所以先 profile 确认瓶颈在框架层再换

三、怎么用与版本变迁

★ 安装:
  pip install uvloop          # Linux / macOS
  pip install winloop         # Windows(uvloop 的移植)

★ 三代写法(★注意版本弃用★):
  ① 最老(0.x 早期):
     import asyncio, uvloop
     asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
     asyncio.run(main())
  ② 简化版(uvloop 0.15+):
     uvloop.install()          # ★内部就是设置 policy★
     asyncio.run(main())
     ★ uvloop 0.21 起 install() 会发 DeprecationWarning★
  ③ ★现在推荐(Python 3.12+ / uvloop 0.18+)★:
     asyncio.run(main(), loop_factory=uvloop.new_event_loop)
     ★ 不依赖全局 policy,作用域清晰★

★ 为什么 policy 被弃用:
  EventLoopPolicy 是一个★全局单例★,用来决定"get_event_loop 返回什么"
  → 与 asyncio.run 的"显式创建/关闭 loop"模型重复且冲突
  → ★Python 3.12 起 policy 相关 API 标记弃用,3.14 加强警告,3.16 计划移除★
  → 新代码一律用 loop_factory 或直接 new_event_loop()

★ 框架里怎么开:
  uvicorn:
    uvicorn app:app --loop uvloop      # ★--loop auto(默认)会自动检测★
    → 装了 uvloop 就自动用,不用改代码
  gunicorn + uvicorn worker:
    gunicorn app:app -k uvicorn.workers.UvicornWorker
  aiohttp:
    web.run_app(app, loop=uvloop.new_event_loop())
  Starlette/FastAPI:
    ★由 uvicorn 决定★,安装 uvloop 即可(uvicorn[standard] 自带)

★ 优雅降级(★跨平台项目必备★):
  def make_loop_factory():
      try:
          import uvloop
          return uvloop.new_event_loop
      except ImportError:
          try:
              import winloop
              return winloop.new_event_loop
          except ImportError:
              return None                    # ★用默认★
  factory = make_loop_factory()
  asyncio.run(main(), loop_factory=factory) if factory else asyncio.run(main())

★ 怎么确认真的生效了:
  loop = asyncio.get_running_loop()
  print(type(loop).__module__)      # 'uvloop' 说明生效
  ★ 别只看"装了包"——policy/factory 没设的话仍然用的是标准实现

用法上要注意三代写法的演进:最老的是设 EventLoopPolicy、然后是 uvloop.install()0.21 起会发 DeprecationWarning)、现在推荐 asyncio.run(main(), loop_factory=uvloop.new_event_loop)(Python 3.12+ 支持,作用域清晰、不依赖全局状态)。policy 被弃用的原因是它是一个全局单例、与 asyncio.run 的「显式创建和关闭 loop」模型重复且冲突——3.12 标记弃用、3.16 计划移除。框架层面通常不用自己写:uvicorn 的 --loop auto(默认)会自动检测,装了 uvloop 就用(uvicorn[standard] 已经自带)。跨平台项目要写优雅降级(try uvloop → try winloop → 回退默认)。最后一个实用提醒:确认它真的生效了——打印 type(asyncio.get_running_loop()).__module__,别只看「装了包」,因为没设 factory 的话仍然用的是标准实现。

四、限制与兼容性

★ 限制一:不支持 Windows★
  libuv 在 Windows 上用 IOCP,而 asyncio 的 Proactor 模型接口不同
  → uvloop ★明确不支持 Windows★
  ✓ 替代:★winloop★(uvloop 的 Windows 移植,API 兼容)
  ✓ 或写降级代码

★ 限制二:某些 API 行为差异★
  uvloop 实现了 AbstractEventLoop 的绝大部分,但不是 100% 一致:
  - ★部分 loop 的私有/低层 API 不存在★(如 _selector 相关)
  - add_reader/add_writer 支持但语义略有差异
  - ★某些错误的异常类型/消息不同★
  - 早期版本对 SSL 的处理有过差异(现已基本对齐)
  → ★依赖 loop 内部细节的库可能不兼容★
  ✓ 换之后要跑一遍完整测试

★ 限制三:调试工具的兼容性★
  - ★debug 模式(PYTHONASYNCIODEBUG)在 uvloop 下部分功能受限★
    (慢回调检测等由标准实现提供的诊断能力可能缺失或不同)
  - 某些 profiler/tracer 对 Cython 代码的支持较弱
  ✓ ★排查问题时可以临时切回标准实现★对比
    → 这也是"用 loop_factory 而不是全局 policy"的好处:容易切换

★ 限制四:不解决 CPU 密集问题★
  uvloop 只加速 IO 和调度
  → 你的 JSON 解析、模板渲染、加密、图像处理★一点都不会变快★
  → ★阻塞事件循环的同步代码在 uvloop 下同样会阻塞★

★ 限制五:额外的依赖和构建★
  - 是 C 扩展 → ★需要对应平台的 wheel 或本地编译★
  - Alpine(musl)等特殊环境可能要自己编译
  - ★增加了一个可能出问题的依赖★(虽然 uvloop 很稳定)

★ 什么时候不该用:
  ✗ 瓶颈不在事件循环(★绝大多数业务系统★)
  ✗ 需要 Windows 支持且不想加 winloop
  ✗ 用了依赖 loop 内部实现的库
  ✗ 团队没有能力排查 C 扩展层面的问题
  ✓ 什么时候该用:
    - ★高吞吐的网关/代理/长连接服务★(每秒几万请求、每个请求很轻)
    - 已经 profile 确认框架开销占比高
    - 用的是 uvicorn 等已经集成好的框架(★几乎零成本★)

uvloop 有五个限制。① 不支持 Windows(libuv 用 IOCP,与 asyncio 的 Proactor 模型接口不同)——替代品是 winloop② 某些 API 行为差异:它实现了绝大部分接口但不是 100% 一致(部分低层私有 API 不存在、异常类型和消息可能不同),依赖 loop 内部细节的库可能不兼容,换之后要跑完整测试。③ 调试工具的兼容性debug 模式的部分诊断能力(如慢回调检测)在 uvloop 下可能受限,排查问题时可以临时切回标准实现对比——这正是「用 loop_factory 而不是全局 policy」的好处(容易切换)。④ 不解决 CPU 密集问题:它只加速 IO 和调度,阻塞事件循环的同步代码在 uvloop 下照样阻塞⑤ 额外的 C 扩展依赖(Alpine 等环境可能要自己编译)。判断标准很清晰:高吞吐的网关/代理/长连接服务值得用,或者框架已经集成好(uvicorn)时几乎零成本;而绝大多数瓶颈不在事件循环的业务系统,换了没有意义

五、真正该做的性能优化

★ 换 uvloop 之前,先确认瓶颈在哪:
  ① ★开 debug 模式看慢回调★ → 有没有同步阻塞代码
  ② ★事件循环滞后探针★ → 循环是不是被占住了
  ③ py-spy top → CPU 花在哪个函数
  ④ 看请求耗时分解:★框架开销 vs 业务逻辑 vs 下游等待★

★ 优先级远高于 uvloop 的优化(★按收益排序★):
  ① ★消除阻塞事件循环的同步调用★
     一次 requests.get() 就能吃掉 uvloop 带来的全部收益
     → 收益:★可能是数量级的★
  ② ★连接池复用★
     每次新建 TCP + TLS 握手 ≈ 几十~几百毫秒
     → aiohttp.ClientSession 复用、DB 连接池
  ③ ★减少下游调用次数★
     批量化、缓存、并发化(gather)
  ④ ★序列化优化★
     orjson 比标准 json ★快 3~10 倍★(JSON 大时收益明显)
  ⑤ ★合理的并发度★
     Semaphore 太小 → 吞吐上不去;太大 → 排队和资源耗尽
  ⑥ ★换 uvloop★  ← ★排在这里★
  ⑦ 多进程(worker)水平扩展

★ 一个真实的性能分解例子(单请求 25ms):
  框架开销(事件循环 + HTTP 解析)  ★0.3ms★    ← uvloop 能优化的部分
  业务逻辑(Python 计算)            2ms
  数据库查询                         15ms
  下游 API 调用                      7ms
  序列化                             0.7ms
  → uvloop 最多省下 ★0.2ms(0.8%)★
  → ★而把数据库查询从 15ms 优化到 5ms 能省 40%★

★ 什么情况下 uvloop 才是关键:
  网关/代理:每个请求只是转发,框架开销占 ★50%+★
  长连接推送:几十万连接,事件循环的调度开销是主要成本
  高频小消息:每秒几十万条,单条处理时间是微秒级
  → ★这些场景下 uvloop 的 2~4 倍是实打实的★

★ 正确的心态:
  ★uvloop 是"顺手就能拿到的免费性能"(尤其框架已集成时),
    但★不要指望它解决架构层面的性能问题★。★
  → 装上不会有坏处(除了 Windows 和调试差异)
  → 但优化的重点应该放在"减少 IO 次数、消除阻塞、复用连接"上

换 uvloop 之前必须先确认瓶颈在哪:开 debug 模式看慢回调、用滞后探针看循环是否被占住、py-spy top 看 CPU 分布、把请求耗时分解成「框架开销 / 业务逻辑 / 下游等待」。优先级远高于 uvloop 的优化按收益排序是:消除阻塞事件循环的同步调用(一次 requests.get() 就能吃掉 uvloop 的全部收益,收益可能是数量级的)、连接池复用(避免每次重做 TCP + TLS 握手)、减少下游调用次数(批量化、缓存、并发化)、序列化优化orjson 比标准库快 310 倍)、合理的并发度然后才是 uvloop。那个真实的分解例子很有说服力:单请求 25ms 中,框架开销只有 0.3ms,uvloop 最多省 0.2ms(0.8%),而把数据库查询从 15ms 优化到 5ms 能省 40%。只有网关/代理、长连接推送、高频小消息这类「框架开销占 50%+」的场景,uvloop 的 24 倍才是实打实的。心态上:它是「顺手就能拿到的免费性能」(尤其框架已集成时),但不要指望它解决架构层面的问题

六、选型建议与其他实现

★ 决策树:
  用 uvicorn/FastAPI 且在 Linux 部署?
    → ★装 uvicorn[standard] 就自带了,什么都不用做★
  自己写的 asyncio 服务,且已 profile 确认框架开销占比高?
    → ★用 loop_factory 换 uvloop★
  瓶颈在数据库/下游/CPU?
    → ★别折腾,去优化真正的瓶颈★
  需要 Windows 支持?
    → ★winloop 或写降级代码★
  依赖了操作 loop 内部的库 / 需要完整的 debug 能力?
    → ★保持标准实现★

★ 其他事件循环实现:
  ★winloop★     uvloop 的 Windows 移植,API 兼容
  ★rloop★       Rust 实现的实验性 loop(较新,生态未成熟)
  ★trio + anyio★ ★不是"换 loop",而是另一套并发模型★
                 trio 主打结构化并发,anyio 提供跨 asyncio/trio 的统一 API
                 → 想要更严格的结构化并发可以看 trio,
                   但★生态兼容性远不如 asyncio★

★ 与其他"提速"手段的关系:
  ┌────────────────┬────────────────────────────────────┐
  │ 手段            │ 解决什么                             │
  ├────────────────┼────────────────────────────────────┤
  │ ★uvloop★       │ 事件循环 + 传输层的开销               │
  │ ★orjson★       │ ★JSON 序列化(常常收益更大)★        │
  │ httptools       │ HTTP 解析(uvicorn[standard] 自带)  │
  │ ★多 worker★    │ ★利用多核(收益通常远大于 uvloop)★  │
  │ 连接池          │ 避免重复握手                         │
  │ 缓存            │ ★减少 IO 次数(收益最大)★           │
  └────────────────┴────────────────────────────────────┘
  ★ uvicorn[standard] = uvicorn + uvloop + httptools + websockets…
    → ★装它就等于同时拿到了几项优化★

★ 验证效果的正确方法:
  ① ★压测对比★:同样的负载,分别用标准实现和 uvloop
     wrk / hey / locust,看 ★QPS 和 P99★
  ② ★用真实业务逻辑压测★,别用 hello world
     (hello world 的提升数字对你毫无参考价值)
  ③ 观察 CPU 使用率:uvloop 通常在★同样 QPS 下 CPU 更低★
  ④ 跑完整的测试套件确认没有兼容问题

★ 一句话建议:
  ★"如果你在用 uvicorn,装 uvicorn[standard] 就已经用上了;
    如果是自己写的服务,先 profile——瓶颈在框架层才值得换,
    而绝大多数业务系统的瓶颈都不在那里。"★

选型的决策树很简单:用 uvicorn/FastAPI 且部署在 Linux 上——装 uvicorn[standard] 就自带了,什么都不用做自己写的 asyncio 服务且已 profile 确认框架开销占比高——用 loop_factory 换 uvloop瓶颈在数据库/下游/CPU——别折腾,去优化真正的瓶颈需要 Windows——用 winloop 或写降级代码。其他实现里:winloop 是 Windows 移植、rloop 是实验性的 Rust 实现、而 trio + anyio 不是「换 loop」而是另一套并发模型(主打结构化并发,但生态兼容性远不如 asyncio)。要注意 uvicorn[standard] = uvicorn + uvloop + httptools + websockets,装它等于同时拿到几项优化——其中 orjson 和多 worker 的收益常常比 uvloop 更大。验证效果要用真实业务逻辑压测(hello world 的提升数字毫无参考价值),看 QPS、P99 和同等 QPS 下的 CPU 使用率

记忆钩子:「asyncio 的★事件循环是可替换的★——你的 async/await 代码建立在 AbstractEventLoop 接口之上,换实现★业务代码一行都不用改★。标准库有两个:SelectorEventLoop(epoll/kqueue,Unix 默认)和 ProactorEventLoop(IOCP,★Windows 默认且只有它支持子进程★)。★uvloop = Cython 封装的 libuv★(Node.js 底层那个 C 库),三处提速:①事件循环核心(回调分发、定时器堆全在 C 层)②★Transport 层(提升最大)★——libuv 直接读进缓冲区一次性交给 Python,省掉大量 Python 函数调用 ③缓冲区复用。★官方宣称 24 倍,但基准测的是纯 TCP echo/静态响应★,真实业务的经验值是:纯框架开销 24 倍、简单 JSON API 1.3~2 倍、★带一次数据库查询不到 10%、调下游服务几乎无提升、CPU 密集为 0★——判断标准是★单请求耗时 <100μs 收益明显、>10ms 基本没用★。用法:★3.12+ 推荐 asyncio.run(main(), loop_factory=uvloop.new_event_loop)★,因为 ★uvloop.install() 和整个 EventLoopPolicy 体系已被弃用★(3.12 标记、3.16 计划移除);uvicorn 的 —loop auto 会自动检测,★uvicorn[standard] 已自带★。五个限制:★不支持 Windows★(用 winloop)、部分低层 API 和异常有差异(依赖 loop 内部的库可能不兼容)、★debug 模式的慢回调检测等诊断能力可能受限★(所以用 loop_factory 便于临时切回对比)、★不解决 CPU 密集和同步阻塞★(阻塞代码在 uvloop 下照样阻塞)、多一个 C 扩展依赖。★最重要的判断:换之前先 profile★——真实例子里单请求 25ms 中框架开销只占 0.3ms,uvloop 最多省 0.8%,而把数据库查询从 15ms 优化到 5ms 能省 40%;优先级更高的优化依次是★消除同步阻塞调用 > 连接池复用 > 减少下游调用 > orjson > 合理并发度 > 才轮到 uvloop★。」

七、常见误区与追问

  • 误区:装上 uvloop 之后异步程序就会快 2~4 倍。 这个数字来自官方基准,而基准测的是纯 TCP echo 和静态 HTTP 响应——在那种场景里「事件循环 + 传输层」就是全部工作量,所以提升才显著。真实业务里的分解是这样的:一个耗时 25ms 的请求,框架开销可能只有 0.3ms(其余是数据库 15ms、下游 API 7ms、业务逻辑 2ms、序列化 0.7ms),uvloop 最多省下 0.2ms,也就是 0.8%。判断标准很直接:单个请求的处理耗时小于 100 微秒时收益明显,超过 10 毫秒基本没用。所以正确顺序是先 profile 确认瓶颈在框架层,再考虑换——绝大多数业务系统的瓶颈都在数据库、下游服务或 CPU 计算上。
  • 误区:用了 uvloop 就不用担心阻塞事件循环的问题了。 uvloop 只加速「事件循环调度和网络传输」,完全不改变 Python 的执行模型——它依然是单线程的事件循环,一个 time.sleep(5)、一次 requests.get()、一段大 JSON 解析或密集正则匹配,照样会把整个循环卡住 5 秒。事实上,因为 uvloop 让框架层变快了,同步阻塞代码在总耗时中的占比会更加突出——一次 50ms 的同步调用可能吃掉 uvloop 带来的全部收益还不止。所以「消除阻塞事件循环的同步调用」这项优化的优先级远高于换 uvloop:前者的收益可能是数量级的,后者通常只有百分之几。
  • 误区:uvloop.install() 是标准用法,照着文档写就行。 它在 uvloop 0.21 起已被弃用(会发 DeprecationWarning)。原因是它的实现方式是设置全局的 asyncio.EventLoopPolicy,而 Python 3.12 起整个 EventLoopPolicy 体系被标记为弃用、3.16 计划移除——因为它是一个全局单例,与 asyncio.run() 的「显式创建和关闭 loop」模型重复且容易冲突(比如库和应用互相覆盖 policy)。新写法是 asyncio.run(main(), loop_factory=uvloop.new_event_loop)(Python 3.12+ 的 asyncio.run 支持 loop_factory 参数),它的作用域清晰、不污染全局状态,而且便于在排查问题时临时切回标准实现做对比。需要复用 loop 时可以用 uvloop.new_event_loop() 手动创建。
  • 误区:uvloop 是 asyncio 的完全替代品,换了不用测试。 它实现了 AbstractEventLoop绝大部分接口,但不是 100% 一致:一些低层或私有 API(如 _selector 相关)在 uvloop 上不存在,add_reader/add_writer 的语义略有差异,某些错误的异常类型和消息不同,早期版本在 SSL 处理上也有过差异。所以依赖 loop 内部实现细节的第三方库可能出问题。另外一个实际影响是调试能力PYTHONASYNCIODEBUG 下的部分诊断功能(比如慢回调检测)由标准实现提供,在 uvloop 下可能缺失或行为不同——这也是推荐用 loop_factory 而不是全局 policy 的原因之一(排查问题时可以一行代码切回标准实现对比)。换之后一定要跑完整的测试套件
  • 误区:Windows 上装不了 uvloop 就没办法优化事件循环了。 有替代方案。winloop 是 uvloop 的 Windows 移植,API 基本兼容(winloop.new_event_loop),底层同样基于 libuv 的 Windows 后端。跨平台项目的标准写法是优雅降级try: import uvloop → except ImportError: try: import winloop → except ImportError: 用标准实现,把得到的 factory 传给 asyncio.run(main(), loop_factory=...)。另外要记住 Windows 上的一个硬性约束只有 ProactorEventLoop 支持子进程(3.8 起是默认),如果因为某个库的要求切到了 SelectorEventLoopasyncio.create_subprocess_* 就会抛 NotImplementedError——这个坑和 uvloop 无关但经常一起被问到。
  • 追问:uvloop 为什么能比纯 Python 的事件循环快这么多? 关键在于把「每秒执行几十万次的热路径」从 Python 层挪到了 C 层。标准实现的一次网络读取大致要经历:epoll 返回就绪 fd → Python 层遍历事件列表、查字典找到对应的回调对象Python 函数调用执行回调 → 调用 sock.recv()创建 Python bytes 对象 → 再调用 protocol.data_received()(又一次 Python 函数调用)。每一步单看只有几微秒,但每秒几十万次就累积成了可观的开销,而且每次 Python 函数调用都涉及栈帧创建、参数打包、引用计数增减。uvloop 用 Cython 把这整条链路编译成 C:事件分发、定时器堆(不再是 Python 的 heapq)、缓冲区管理都在 C 层完成,只在「把完整数据交给你的协程」时才进入 Python。此外 libuv 本身也做了很多优化(批量处理就绪事件、复用缓冲区减少 GC 压力)。这也解释了为什么业务逻辑越重,uvloop 的相对收益越小——它优化的那部分在总耗时中的占比被稀释了。
  • 追问:除了换 uvloop,还有哪些「换个库就能提速」的手段? 几个收益明确的:orjson 替代标准 json——序列化快 3~10 倍,返回大 JSON 的 API 收益常常超过 uvloop(注意它返回 bytes 而不是 str,且对 dataclassdatetimenumpy 有原生支持)。httptools 替代 Python 实现的 HTTP 解析(uvicorn[standard] 自带)。③ 数据库驱动——asyncpgpsycopg2 + 线程池快数倍(它是纯异步且用二进制协议)。④ 多 worker 进程——这是利用多核的唯一方式,收益通常远大于所有单进程优化的总和(uvicorn 的 --workers N 或 gunicorn)。⑤ 连接池和缓存——避免重复的 TCP+TLS 握手、减少 IO 次数,这类「架构级」优化的收益往往是数量级的。最省事的做法是直接装 uvicorn[standard],它同时包含了 uvloop、httptools、websockets 等优化组件。
  • 追问:怎么正确验证换 uvloop 之后的效果? 四个要点。① 用真实业务逻辑压测,不要用 hello world——hello world 的提升数字(可能 3 倍)对你的服务毫无参考价值,因为它没有数据库、没有下游调用、没有序列化。② 对比要控制变量:同一台机器、同样的负载模型、同样的 worker 数,只换 loop 实现;用 wrk/hey/locust 施压,同时记录 QPS 和 P99 延迟(只看 QPS 可能掩盖尾延迟恶化)。③ 观察 CPU 使用率——uvloop 的典型表现是在相同 QPS 下 CPU 占用更低,这意味着同样的机器能承载更多流量,有时这比 QPS 数字更有意义。④ 跑完整的测试套件确认没有兼容性问题(尤其是用了操作 loop 底层 API 的库时)。另外建议先做一次 profile 分解请求耗时(框架开销 / 业务逻辑 / 下游等待各占多少),如果框架开销占比不到 5%,那么压测前就可以预判提升不会超过 5%,也就没必要折腾了。

八、加强记忆

asyncio 的事件循环是可替换的——你的 async/await 代码建立在 AbstractEventLoop 接口之上,换实现业务代码一行都不用改。标准库有两个实现:SelectorEventLoop(epoll/kqueue,Unix 默认)和 ProactorEventLoop(IOCP,Windows 默认,且只有它支持子进程)。uvloop = Cython 封装的 libuv(Node.js 底层那个 C 库),三处提速:① 事件循环核心(回调分发、定时器堆全在 C 层)、② Transport 层(提升最大)——libuv 直接把数据读进缓冲区一次性交给 Python,省掉大量 Python 函数调用和中间对象、③ 缓冲区复用减少 GC 压力官方宣称 2~4 倍,但基准测的是纯 TCP echo 和静态响应;真实业务的经验值是:纯框架开销场景 24 倍、简单 JSON API 1.32 倍、带一次数据库查询不到 10%、调用下游服务几乎无提升、CPU 密集为 0——判断标准是单请求耗时小于 100μs 收益明显、大于 10ms 基本没用。用法上,3.12+ 推荐 asyncio.run(main(), loop_factory=uvloop.new_event_loop),因为 uvloop.install() 和整个 EventLoopPolicy 体系已被弃用(3.12 标记、3.16 计划移除);uvicorn 的 --loop auto 会自动检测,uvicorn[standard] 已经自带五个限制不支持 Windows(用 winloop)、部分低层 API 和异常类型有差异(依赖 loop 内部的库可能不兼容)、debug 模式的慢回调检测等诊断能力可能受限(所以用 loop_factory 便于临时切回对比)、不解决 CPU 密集和同步阻塞(阻塞代码在 uvloop 下照样把循环卡住,而且占比会更突出)、多一个 C 扩展依赖。最重要的判断是:换之前先 profile——真实例子里单请求 25ms 中框架开销只占 0.3ms,uvloop 最多省 0.8%,而把数据库查询从 15ms 优化到 5ms 能省 40%;优先级更高的优化依次是消除同步阻塞调用 > 连接池复用 > 减少下游调用次数 > orjson > 合理的并发度 > 多 worker 利用多核,然后才轮到 uvloop。