Zpět na domů

Picows: nejrychlejší WebSocket pro asyncio bez kompromisů

Analýza architektury knihovny picows — nejrychlejšího WebSocket enginu pro asyncio. Rozbor zero-copy zpracování, callback rozhraní a odmítnutí async v hot-path. Příklady kódu a srovnání výkonu.

Picows: proč je to nejrychlejší WebSocket v Pythonu?
Advertisement 728x90

# Jak se picows stal nejrychlejším WebSocket motorem pro asyncio: architektura bez kompromisů

Knihovna picows přepsala pravidla výkonu WebSocketů v Pythonu tím, že se vzdala pohodlí ve prospěch rychlosti. Místo async for a automatického sestavování zpráv nabízí přímý přístup k rámcům přes callback rozhraní, zero-copy zpracování a plnou kontrolu nad buffery. Pro algoritmický trading, kde každá milisekunda stojí peníze, to není optimalizace – to je nutnost.

Proč standardní řešení nestačí

Když jde o vysokofrekvenční zpracování dat – například aktualizace z kryptoburz – aiohttp a websockets vykazují nepřijatelné zpoždění. Při zátěži 10–20 tisíc zpráv za sekundu (každá po 200 bajtů) začnou hromadit data ve vnitřních frontách. Zpoždění mezi přijetím paketu ze sítě a jeho doručením do business logiky může dosáhnout 100 ms – fatální pro obchodní strategie.

Problém není v Pythonu jako takovém, ale v architektuře:

Google AdInline article slot
  • Data se opakovaně kopírují: ze soketu → do bufferu parseru → do fronty → do uživatelské korutiny.
  • Parsery běží v Pythonu (kromě částečně přepsaného C-parseru v aiohttp), což vytváří režii.
  • TEXT zprávy se konvertují na str, což vyžaduje další alokaci paměti.
  • Odesílání velkých zpráv (např. 1 MB) vyžaduje kopírování celého payloadu jen kvůli přidání 2–14 bajtů hlavičky.

To není jen pomalé – je to předvídatelně neefektivní. Zejména při použití asyncio.Protocol, který vždy předává nové objekty bytes místo rychlejšího BufferedProtocol, který umožňuje práci s externím bufferem.

Architektura picows: zero-copy a minimální abstrakce

Picows je postavený na principech, které by v jiných knihovnách považovaly za příliš nízkou úroveň:

  • Používá asyncio.BufferedProtocol pro minimalizaci kopírování při čtení.
  • Parser v C rozkládá hlavičku rámce a ihned předává hranice payloadu do uživatelského kódu – bez vytváření meziproduktů.
  • Uživatel pracuje přímo s memoryview nebo bytearray – bez konverze na str, pokud není potřeba.
  • Odesílání je realizováno přes send_reuse_external_bytearray – hlavička se zapíše přímo do rezervovaného prostoru před payloadem, maskování se aplikuje in-place.

Vzdání se pohodlí nebylo snadné, ale je oprávněné:

Google AdInline article slot
  • Žádné async for – místo toho callback on_ws_frame.
  • Žádná automatická sestava multi-frame zpráv – uživatel sám rozhodne, jak je spojit.
  • Žádná vestavěná podpora permessage-deflate – komprese se musí řešit na úrovni aplikace.
  • send() není async – pokud je socket obsazený, data jdou do fronty bez čekání.

To není bug, ale feature: volání async funkce na každém rámu přidává desítky mikrosekund zpoždění. Picows umožňuje zpracovávat rámy bez přepínání kontextu asyncio.

Příklady kódu: klient a server na picows

Minimální klient:

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

Všimněte si: žádné await uvnitř on_ws_frame. Zpracování probíhá synchronně bez přepínání kontextu. Pokud potřebujete asynchronní chování – můžete použít create_task nebo Queue, ale to je vědomá volba vývojáře, ne vnucená architektura.

Benchmarky: kde picows nechává konkurenty pozadu

Testy ukazují rozdíl v RPS (requests per second) při echo výměně zpráv různé velikosti:

  • Pro malé zprávy (64–512 bajtů) picows překonává aiohttp 3–5×, websockets 8–10×.
  • Pro velké zprávy (1 MB+) je výhoda ještě větší – až 20× díky zero-copy odesílání.
  • Zpoždění mezi přijetím a zpracováním rámce – stabilně pod 100 mikrosekund i při zátěži 50K RPS.

Klíčové faktory:

  • Absence zbytečného kopírování – data se čtou a píšou in-place.
  • Minimální tvorba objektů – žádné meziprodukty str, list, dict.
  • Nulová abstrakce při zpracování rámců – callback se volá hned po parsování hlavičky.
  • Optimalizovaný C kód – celé parsování a tvorba rámců běží v C bez zásahu Python interpretu.

Co je důležité

  • Picows není pro každého – je pro ty, kteří potřebují maximální výkon, ne pohodlné API.
  • Architektura vychází z transportního modelu asyncio: transport/protocol, ne z vysoceúrovňových abstrakcí.
  • Zero-copy zpracování a direct memory access – klíčové výhody oproti konkurenci.
  • Vzdání se async v hot-path snížilo zpoždění o řády.
  • Podpora TLS a složitých scénářů (multi-frame, masking) bez ztráty rychlosti.

Kdy volit picows, a kdy ne

Používejte picows, pokud:

  • Je pro vás kritická latence < 1 ms při zpracování tisíců zpráv za sekundu.
  • Jste ochotni obětovat pohodlí kvůli kontrole nad pamětí a výkonem.
  • Vaše logika lze realizovat bez neustálého používání async/await v obsluze rámců.
  • Pracujete s binárními daty nebo JSON, který lze parsovat přímo z memoryview.

Nepoužívejte, pokud:

  • Je pro vás důležitá jednoduchost API a rychlý start.
  • Očekáváte automatickou sestavu multi-frame zpráv.
  • Potřebujete vestavěnou podporu komprese nebo WebSocket rozšíření.
  • Vaše zátěž je nízká (< 1K RPS) a nechcete komplikovat architekturu.

Picows není náhradou za aiohttp nebo websockets. Je to specializovaný nástroj pro edge-case scénáře, kde jede o každý takt procesoru. Pro algoritmický trading, herní servery, IoT brány a high-frequency data pipelines – jedna z nejlepších voleb v Python ekosystému.

— Editorial Team

Advertisement 728x90

Číst dál