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:
Marcelo Trylesinski 2026-03-16 05:12:05 +01:00 committed by GitHub
parent 9dbb7836bb
commit d8f2501316
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 31 additions and 21 deletions

View File

@ -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]

View File

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

View File

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

View File

@ -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]