Zurück zur Startseite

Picows: der schnellste WebSocket für asyncio ohne Kompromisse

Analyse der picows-Bibliotheksarchitektur — der schnellste WebSocket-Engine für asyncio. Aufschlüsselung der zero-copy-Verarbeitung, Callback-Schnittstelle und Ablehnung von async im Hot-Path. Code-Beispiele und Leistungsvergleich.

Picows: Warum ist es der schnellste WebSocket in Python?
Advertisement 728x90

# Wie picows zum schnellsten WebSocket-Engine für asyncio wurde: Eine Architektur ohne Kompromisse

Die picows-Bibliothek hat die Regeln für WebSocket-Performance in Python neu geschrieben und Komfort zugunsten purer Geschwindigkeit über Bord geworfen. Vergessen Sie async for und automatische Nachrichtenmontage – Sie erhalten direkten Zugriff auf Frames über eine Callback-Schnittstelle, Zero-Copy-Verarbeitung und volle Kontrolle über Puffer. Für algorithmisches Trading, wo jede Millisekunde Geld kostet, ist das keine bloße Optimierung – es ist eine Notwendigkeit.

Warum Standardlösungen nicht ausreichen

Bei der Verarbeitung hochfrequentierter Daten – etwa Marktdaten von Krypto-Börsen – liefern aiohttp und websockets unakzeptabel hohe Latenzen. Bei einer Last von 10–20 Tausend Nachrichten pro Sekunde (je 200 Bytes) häufen sich die Daten in internen Queues an. Die Verzögerung zwischen dem Empfang eines Pakets aus dem Netzwerk und der Übergabe an Ihre Geschäftslogik kann 100 ms erreichen – fatal für Trading-Strategien.

Das Problem liegt nicht am Python selbst, sondern an der Architektur:

Google AdInline article slot
  • Daten werden mehrmals kopiert: von Socket → Parser-Puffer → Queue → User-Coroutine.
  • Parser laufen in Python (außer dem teilweise in C umgeschriebenen Parser in aiohttp), was Overhead erzeugt.
  • TEXT-Nachrichten werden zu str konvertiert, was zusätzliche Speicherallokierung erfordert.
  • Das Senden großer Nachrichten (z. B. 1 MB) erfordert das Kopieren der gesamten Nutzlast nur um einen 2–14-Byte-Header hinzuzufügen.

Das ist nicht nur langsam – es ist vorhersehbar ineffizient. Besonders bei asyncio.Protocol, das immer neue bytes-Objekte übergibt statt des schnelleren BufferedProtocol, das mit externen Puffern arbeitet.

picows-Architektur: Zero-Copy und minimale Abstraktion

Picows basiert auf Prinzipien, die andere Bibliotheken als zu tief hängende Früchte ansehen würden:

  • Verwendet asyncio.BufferedProtocol, um Kopien beim Lesen zu minimieren.
  • Ein C-Parser zerlegt den Frame-Header und übergibt sofort die Nutzlastgrenzen an den User-Code – keine Zwischenobjekte.
  • Nutzer arbeiten direkt mit memoryview oder bytearray – keine str-Konvertierung, es sei denn, nötig.
  • Senden erfolgt über send_reuse_external_bytearray – der Header wird direkt in den reservierten Platz vor der Nutzlast geschrieben, Maskierung in-place.

Den Verzicht auf Annehmlichkeiten hat man nicht leichtfertig vollzogen, aber es lohnt sich:

Google AdInline article slot
  • Kein async for – stattdessen ein Callback on_ws_frame.
  • Keine automatische Montage von Multi-Frame-Nachrichten – Nutzer entscheiden, wie sie konkateniert werden.
  • Kein integrierter permessage-deflate-Support – Kompression auf App-Ebene handhaben.
  • send() ist nicht async – bei busy Socket landet Daten in einer Queue, ohne zu blockieren.

Das ist kein Bug, das ist ein Feature: Ein asynchroner Funktionsaufruf pro Frame fügt Zehn-Mikrosekunden-Latenz hinzu. Picows erlaubt die Frame-Verarbeitung ohne asyncio-Kontextwechsel.

Code-Beispiele: Client und Server mit picows

Minimaler Client:

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-Server:

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())

Beachten Sie: Kein await innerhalb von on_ws_frame. Die Verarbeitung läuft synchron, ohne Kontextwechsel. Für asynchrones Verhalten create_task oder Queue nutzen – das ist eine bewusste Entwicklerentscheidung, nicht architekturnötig.

Benchmarks: Wo picows die Konkurrenz abhängt

Tests zeigen den Unterschied in Anfragen pro Sekunde für Echo-Austausche mit Nachrichten unterschiedlicher Größe:

  • Bei kleinen Nachrichten (64–512 Bytes) schlägt picows aiohttp um das 3–5-Fache, websockets um das 8–10-Fache.
  • Bei großen Nachrichten (1 MB+) wächst der Vorsprung noch – bis zum 20-Fachen dank Zero-Copy-Senden.
  • Latenz zwischen Empfang und Verarbeitung eines Frames bleibt unter 100 Mikrosekunden selbst bei 50.000 Anfragen pro Sekunde.

Schlüsselfaktoren:

  • Keine unnötigen Kopien – Daten werden in-place gelesen und geschrieben.
  • Minimale Objekterzeugung – keine Zwischenschritte mit str, Liste oder Dict.
  • Keine Abstraktion bei Frame-Handling – Callback feuert direkt nach Header-Parsing.
  • Optimierter C-Code – alles Parsing und Frame-Bau in C, ohne Python-Interpreter.

Wichtige Erkenntnisse

  • Picows ist nicht für jeden – es ist für die, die maximale Performance über eine bequeme API stellen.
  • Architektur folgt dem asyncio-Transport-Modell: transport/protocol, keine High-Level-Abstraktionen.
  • Zero-Copy-Verarbeitung und direkter Speicherzugriff sind die Killer-Vorteile gegenüber der Konkurrenz.
  • Verzicht auf async im Hot Path hat Latenz um Größenordnungen gesenkt.
  • TLS-Support und fortgeschrittene Szenarien (Multi-Frame, Maskierung) ohne Geschwindigkeitsverlust.

Wann picows wählen – und wann nicht

Picows nutzen, wenn:

  • Latenz unter 1 ms bei Tausenden Nachrichten pro Sekunde entscheidend ist.
  • Sie Komfort gegen Kontrolle über Speicher und Performance eintauschen.
  • Ihre Logik ohne ständiges async/await im Frame-Handler läuft.
  • Sie mit Binärdaten oder JSON arbeiten, das direkt aus memoryview geparst werden kann.

Vermeiden, wenn:

  • Sie eine einfache API und schnelle Starts priorisieren.
  • Sie automatische Multi-Frame-Nachrichtenmontage erwarten.
  • Sie integrierte Kompression oder WebSocket-Erweiterungen brauchen.
  • Ihre Last gering ist (< 1.000 Anfragen pro Sekunde) und Sie die Architektur nicht komplizieren wollen.

Picows ersetzt nicht aiohttp oder websockets. Es ist ein Spezialwerkzeug für Edge-Cases, wo jeder CPU-Takt zählt. Für algorithmisches Trading, Game-Server, IoT-Gateways und hochfrequente Datenpipelines – eine der besten Optionen im Python-Ökosystem.

— Editorial Team

Advertisement 728x90

Weiterlesen