返回首页

Picows:asyncio 无妥协的最快 WebSocket

picows 库架构分析 — asyncio 的最快 WebSocket 引擎。零拷贝处理、回调接口以及热路径中拒绝 async 的剖析。代码示例和性能比较。

Picows:为什么它是 Python 中最快的 WebSocket?
Advertisement 728x90

# picows 如何成为 asyncio 最快的 WebSocket 引擎:一种无妥协的架构

picows 库重写了 Python 中 WebSocket 性能的规则,舍弃便利性以换取极致速度。忘掉 async for 和自动消息组装吧——通过回调接口直接访问帧、零拷贝处理,以及对缓冲区的完全控制。对于算法交易来说,每毫秒都意味着金钱,这不仅仅是优化——而是必需品。

为什么标准方案不足以胜任

在高频数据处理场景中——例如来自加密货币交易所的市场更新——aiohttp 和 websockets 会带来不可接受的延迟。在每秒 10–20 千条消息(每条 200 字节)的负载下,它们开始在内部队列中堆积数据。从网络接收数据包到将其传递给业务逻辑的延迟可能达到 100 ms——对于交易策略来说,这是致命的。

问题不在于 Python 本身,而是架构设计:

Google AdInline article slot
  • 数据被多次拷贝:从 socket → 解析器缓冲区 → 队列 → 用户协程。
  • 解析器在 Python 中运行(aiohttp 中部分重写的 C 解析器除外),增加了开销。
  • TEXT 消息被转换为 str,需要额外内存分配。
  • 发送大消息(例如 1 MB)需要拷贝整个负载,仅为了添加 2–14 字节的头部。

这不仅仅是慢——而且是可预测的低效。特别是在使用 asyncio.Protocol 时,它总是传递新的 bytes 对象,而不是更快的 BufferedProtocol,后者允许使用外部缓冲区。

picows 架构:零拷贝与最小抽象

picows 基于其他库认为过于底层的设计原则构建:

  • 使用 asyncio.BufferedProtocol 最小化读取时的拷贝。
  • C 解析器分解帧头部,并立即将负载边界传递给用户代码——没有中间对象。
  • 用户直接操作 memoryview 或 bytearray——除非必要,否则不转换为 str。
  • 发送使用 send_reuse_external_bytearray——头部直接写入负载前的预留空间,并就地应用掩码。

放弃这些便利功能并不容易,但值得:

Google AdInline article slot
  • 没有 async for——而是 on_ws_frame 回调。
  • 没有自动多帧消息组装——用户决定如何连接它们。
  • 没有内置 permessage-deflate 支持——在应用层处理压缩。
  • send() 不是异步的——如果 socket 繁忙,数据进入队列而不阻塞。

这不是 bug,而是特性:每个帧调用异步函数会增加数十微秒的延迟。picows 允许在没有 asyncio 上下文切换的情况下处理帧。

代码示例:使用 picows 的客户端和服务器

最小客户端:

import asyncio
from picows import ws_connect, WSFrame, WSTransport, WSListener, WSMsgType, WSCloseCode

class ClientListener(WSListener):
    def on_ws_connected(self, transport: WSTransport):
        transport.send(WSMsgType.TEXT, b"Hello world")

    def on_ws_frame(self, transport: WSTransport, frame: WSFrame):
        print(f"Echo reply: {frame.get_payload_as_ascii_text()}")
        transport.send_close(WSCloseCode.OK)
        transport.disconnect()

async def main():
    transport, client = await ws_connect(ClientListener, "ws://127.0.0.1:9001")
    await transport.wait_disconnected()

if __name__ == '__main__':
    asyncio.run(main())

Echo 服务器:

Google AdInline article slot
import asyncio
from picows import ws_create_server, WSFrame, WSTransport, WSListener, WSMsgType, WSUpgradeRequest

class ServerClientListener(WSListener):
    def on_ws_connected(self, transport: WSTransport):
        print("New client connected")

    def on_ws_frame(self, transport: WSTransport, frame: WSFrame):
        if frame.msg_type == WSMsgType.CLOSE:
            transport.send_close(frame.get_close_code(), frame.get_close_message())
            transport.disconnect()
        else:
            transport.send(frame.msg_type, frame.get_payload_as_memoryview())

async def main():
    def listener_factory(r: WSUpgradeRequest):
        return ServerClientListener()

    server: asyncio.Server = await ws_create_server(listener_factory, "127.0.0.1", 9001)
    for s in server.sockets:
        print(f"Server started on {s.getsockname()}")

    await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

注意:在 on_ws_frame 中没有 await。处理是同步进行的,没有上下文切换。对于异步行为,可以使用 create_task 或 Queue——但这是开发者的 deliberate 选择,而不是架构强制。

基准测试:picows 让竞争对手望尘莫及

测试显示了不同大小消息的 echo 交换中 RPS(每秒请求数)的差异:

  • 对于小消息(64–512 字节),picows 比 aiohttp 快 3–5 倍,比 websockets 快 8–10 倍。
  • 对于大消息(1 MB+),优势更大——高达 20 倍,得益于零拷贝发送。
  • 即使在 50K RPS 下,接收和处理帧之间的延迟也保持在 100 微秒以下。

关键因素:

  • 无不必要拷贝——数据就地读取和写入。
  • 最小对象创建——没有中间 str、list 或 dict。
  • 帧处理零抽象——回调在头部解析后立即触发。
  • 优化的 C 代码——所有解析和帧构建都在 C 中进行,不涉及 Python 解释器。

关键要点

  • picows 并非适合所有人——它适合那些需要最大性能而非便捷 API 的人。
  • 架构遵循 asyncio 的传输模型:transport/protocol,而不是高层抽象。
  • 零拷贝处理和直接内存访问是相对于竞争对手的杀手锏。
  • 在热路径中跳过 async 将延迟降低了几个数量级。
  • TLS 支持和高级场景(多帧、掩码)处理时不损失速度。

何时选择 picows——何时不选

如果:

  • 延迟低于 1 ms 对于每秒处理数千条消息至关重要。
  • 你愿意用对内存和性能的控制换取便利性。
  • 你的逻辑能在帧处理器中不依赖持续的 async/await。
  • 你处理的是二进制数据或可直接从 memoryview 解析的 JSON。

则使用 picows;

否则跳过:

  • 你优先简单 API 和快速上手。
  • 你期望自动多帧消息组装。
  • 你需要内置压缩或 WebSocket 扩展。
  • 你的负载较轻(< 1K RPS)且不想复杂化架构。

picows 不是 aiohttp 或 websockets 的替代品。它是针对边缘案例的专用工具,在每个 CPU 时钟周期都至关重要的场景中大放异彩。对于算法交易、游戏服务器、IoT 网关和高频数据管道——它是 Python 生态中最佳选择之一。

— Editorial Team

Advertisement 728x90

继续阅读