Repository navigation
perf: cut per-request overhead in the client hot path - #182
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two changes to
ClientandAsyncClient, one commit each. Behaviour does not change.Skip the URL query merge when there is nothing to merge
_build_requestparsed every URL withhttpx2.URLbefore handing it to httpx2, which then parsed it again. The extra parse is only needed when a URL query has to survive params (the httpx2 workaround from #146). It now returnsclient.build_request(...)directly in two cases:urlis astrwith no?. httpx2 only takes a query from a literal?, so such a URL has no query to keep.params(absent orNone) and the httpx2 client has noparams. httpx2 then leaves the URL query alone.It still takes the merge path for an
httpx2.URLinstance with params,params={}(httpx2 drops the URL query for any non-Noneparams, including an empty dict), client-level params, and a?that only appears in the fragment (that last one gives the same result either way).tests/test_url_query_merge.pyis unchanged and passes. The newtests/test_url_query_fast_path.pycovers both skip cases and each case where the merge must still run, againstbuild_requestand againstget()with abase_url, in both clients. Those tests also pass againstmain.Map httpx2 exceptions inline
_terminalwrapped every send in_httpx2_exception_mapper/_httpx2_exception_mapper_sync, which are generator-based context managers. Entering and leaving one costs more than atryblock._terminaland bothstream()methods now catch inline:MAPPED_HTTPX2_ERRORS(HTTPError,InvalidURL,CookieConflict) goes throughmap_httpx2_exception(exc)withfrom exc, and aRuntimeErrorbecomesTransportErroronly when the httpx2 client is closed, otherwise it propagates unchanged. Nothing else used the two context managers, so they are gone.stream()is not a hot path; I inlined it too so the context managers could be deleted rather than kept for one caller each.Two new tests in
tests/test_error_mapping_terminal.pycheck that aRuntimeErrorfrom the transport on an open client reaches the caller as the same object, sync and async. Before this change only the shared context manager needed that branch covered, and thestream()tests did it.Numbers
MockTransport,
base_urlset, cap off. Each change was measured in one process, swapping in themainversion of the patched function and alternating with the new one, 15 to 21 rounds of 3 x 500 calls, best round reported. The machine had a load average of 5 to 13 during the runs, so absolute times are 2 to 4 times the quiet-machine audit figures (35 us for a syncget). The deltas were stable across runs; the ranges below cover them.URL fast path (us per call, negative is faster):
get("/x")get("/x", params=...)get("/x?q=1")get("/x?q=1", params=...)(merge path)post(json=...)post(json=..., params=...)Inline exception mapping:
get(any URL shape)post(json=...), with or without paramsBoth together on
get("/x"): -12 to -13 us sync and async under the same load. A plain before/after across separate processes was too noisy on this machine to separate the changes, so I'm not quoting it.Release notes (0.22.0)
None. No behaviour change.