chore: pre-create Config objects in benchmarks to measure protocol hot paths (#2851)
Config.__init__ calls dictConfig() on every construction, which dominated benchmark time (~70% for httptools). Pre-creating configs at module level removes this setup noise so CodSpeed measures the actual protocol work.
This commit is contained in:
parent
9dbb7836bb
commit
d8f2501316
@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any, TypeAlias
|
||||
from uvicorn._types import ASGIApplication, Scope
|
||||
from uvicorn.config import Config
|
||||
from uvicorn.lifespan.off import LifespanOff
|
||||
from uvicorn.lifespan.on import LifespanOn
|
||||
from uvicorn.protocols.http.h11_impl import H11Protocol
|
||||
from uvicorn.server import ServerState
|
||||
|
||||
@ -158,16 +157,17 @@ class MockProtocol(asyncio.Protocol):
|
||||
scope: Scope
|
||||
|
||||
|
||||
def make_config(app: ASGIApplication, **kwargs: Any) -> Config:
|
||||
return Config(app=app, **kwargs)
|
||||
|
||||
|
||||
def get_connected_protocol(
|
||||
app: ASGIApplication,
|
||||
config: Config,
|
||||
http_protocol_cls: type[HTTPProtocol],
|
||||
lifespan: LifespanOff | LifespanOn | None = None,
|
||||
**kwargs: Any,
|
||||
) -> MockProtocol:
|
||||
loop = MockLoop()
|
||||
transport = MockTransport()
|
||||
config = Config(app=app, **kwargs)
|
||||
lifespan = lifespan or LifespanOff(config)
|
||||
lifespan = LifespanOff(config)
|
||||
server_state = ServerState()
|
||||
protocol = http_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop) # type: ignore
|
||||
protocol.connection_made(transport) # type: ignore[arg-type]
|
||||
|
||||
@ -15,6 +15,7 @@ from tests.benchmarks.http import (
|
||||
SIMPLE_POST_REQUEST,
|
||||
START_POST_REQUEST,
|
||||
get_connected_protocol,
|
||||
make_config,
|
||||
)
|
||||
from tests.response import Response
|
||||
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
|
||||
@ -28,6 +29,9 @@ _plain_text_app = Response("Hello, world", media_type="text/plain")
|
||||
_no_content_app = Response(b"", status_code=204)
|
||||
_chunked_app = Response(b"Hello, world!", status_code=200, headers={"transfer-encoding": "chunked"})
|
||||
|
||||
_plain_text_config = make_config(_plain_text_app)
|
||||
_chunked_config = make_config(_chunked_app)
|
||||
|
||||
|
||||
async def _body_echo_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
|
||||
body = b""
|
||||
@ -41,26 +45,29 @@ async def _body_echo_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGIS
|
||||
await send({"type": "http.response.body", "body": body})
|
||||
|
||||
|
||||
_body_echo_config = make_config(_body_echo_app)
|
||||
|
||||
|
||||
async def test_bench_simple_get(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(SIMPLE_GET_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_simple_post(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(SIMPLE_POST_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_large_post(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(LARGE_POST_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_pipelined_requests(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(SIMPLE_GET_REQUEST * 3)
|
||||
await protocol.loop.run_one()
|
||||
await protocol.loop.run_one()
|
||||
@ -68,7 +75,7 @@ async def test_bench_pipelined_requests(http_protocol_cls: type[HTTPProtocol]) -
|
||||
|
||||
|
||||
async def test_bench_keepalive_reuse(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(SIMPLE_GET_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
protocol.data_received(SIMPLE_GET_REQUEST)
|
||||
@ -76,25 +83,25 @@ async def test_bench_keepalive_reuse(http_protocol_cls: type[HTTPProtocol]) -> N
|
||||
|
||||
|
||||
async def test_bench_chunked_response(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_chunked_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_chunked_config, http_protocol_cls)
|
||||
protocol.data_received(SIMPLE_GET_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_http10(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(HTTP10_GET_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_connection_close(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(CONNECTION_CLOSE_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_fragmented_body(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_plain_text_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
|
||||
protocol.data_received(FRAGMENTED_POST_HEADERS)
|
||||
for chunk in FRAGMENTED_BODY_CHUNKS:
|
||||
protocol.data_received(chunk)
|
||||
@ -102,7 +109,7 @@ async def test_bench_fragmented_body(http_protocol_cls: type[HTTPProtocol]) -> N
|
||||
|
||||
|
||||
async def test_bench_post_body_receive(http_protocol_cls: type[HTTPProtocol]) -> None:
|
||||
protocol = get_connected_protocol(_body_echo_app, http_protocol_cls)
|
||||
protocol = get_connected_protocol(_body_echo_config, http_protocol_cls)
|
||||
protocol.data_received(START_POST_REQUEST)
|
||||
protocol.data_received(FINISH_POST_REQUEST)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.benchmarks.http import make_config
|
||||
from tests.benchmarks.ws import WS_UPGRADE, get_connected_ws_protocol
|
||||
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
|
||||
|
||||
@ -47,13 +48,17 @@ async def _ws_send_text_app(scope: Scope, receive: ASGIReceiveCallable, send: AS
|
||||
await send({"type": "websocket.close", "code": 1000})
|
||||
|
||||
|
||||
_ws_accept_close_config = make_config(_ws_accept_close_app, access_log=False)
|
||||
_ws_send_text_config = make_config(_ws_send_text_app, access_log=False)
|
||||
|
||||
|
||||
async def test_bench_ws_handshake(ws_cls: WSProtocolClass) -> None:
|
||||
protocol = get_connected_ws_protocol(_ws_accept_close_app, ws_cls)
|
||||
protocol = get_connected_ws_protocol(_ws_accept_close_config, ws_cls)
|
||||
protocol.data_received(WS_UPGRADE)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
|
||||
async def test_bench_ws_send_text(ws_cls: WSProtocolClass) -> None:
|
||||
protocol = get_connected_ws_protocol(_ws_send_text_app, ws_cls)
|
||||
protocol = get_connected_ws_protocol(_ws_send_text_config, ws_cls)
|
||||
protocol.data_received(WS_UPGRADE)
|
||||
await protocol.loop.run_one()
|
||||
|
||||
@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any, TypeAlias
|
||||
|
||||
from tests.benchmarks.http import MockLoop, MockTransport
|
||||
from uvicorn._types import ASGIApplication
|
||||
from uvicorn.config import Config
|
||||
from uvicorn.lifespan.off import LifespanOff
|
||||
from uvicorn.server import ServerState
|
||||
@ -31,10 +30,9 @@ WS_TEXT_FRAME = b"\x81\x8d\x00\x00\x00\x00Hello, world!"
|
||||
WS_CLOSE_FRAME = b"\x88\x82\x00\x00\x00\x00\x03\xe8"
|
||||
|
||||
|
||||
def get_connected_ws_protocol(app: ASGIApplication, ws_protocol_cls: WSProtocolClass, **kwargs: Any) -> Any:
|
||||
def get_connected_ws_protocol(config: Config, ws_protocol_cls: WSProtocolClass) -> Any:
|
||||
loop = MockLoop()
|
||||
transport = MockTransport()
|
||||
config = Config(app=app, access_log=False, **kwargs)
|
||||
lifespan = LifespanOff(config)
|
||||
server_state = ServerState()
|
||||
protocol = ws_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop) # type: ignore[arg-type]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user