85 lines
4.0 KiB
Markdown
85 lines
4.0 KiB
Markdown
# Performance
|
||||
|
|
|
|||
|
|
`retoor <retoor@molodetz.nl>`
|
|||
|
|
|
|||
|
|
This document records measured overhead of the `context.request`-backed
|
|||
|
|
fast path (`get`/`post`/`put`/`patch`/`delete`/`head`/`download`) against
|
|||
|
|
a plain `aiohttp.ClientSession`, and the fast path's behaviour under
|
|||
|
|
concurrency. All numbers below were measured against public, internet-hosted
|
|||
|
|
endpoints (`httpbin.org`), so absolute times include normal internet
|
|||
|
|
latency and third-party server jitter — the comparison between the two
|
|||
|
|
clients on the same run is the meaningful signal, not the absolute
|
|||
|
|
seconds.
|
|||
|
|
|
|||
|
|
## Single-request latency
|
|||
|
|
|
|||
|
|
| | One-time | Per request (steady state, n=10) |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Browser launch (`start()`) | ~0.96s | — |
|
|||
|
|
| First request (context warm-up) | ~0.47s | — |
|
|||
|
|
| stealthii `get()` | — | avg 0.184s (min 0.101s, max 0.791s) |
|
|||
|
|
| aiohttp `get()` | — | avg 0.168s (min 0.102s, max 0.441s) |
|
|||
|
|
|
|||
|
|
Browser launch and the shared context's warm-up are each paid exactly
|
|||
|
|
once per process — in a host application, at `start()` during startup,
|
|||
|
|
not per request. Steady-state, the fast path costs roughly 15-20ms more
|
|||
|
|
per request than `aiohttp` on average, which is within normal network
|
|||
|
|
jitter for a single request (the observed max-min spread on both clients
|
|||
|
|
individually is far larger than the 15-20ms difference between them).
|
|||
|
|
|
|||
|
|
## Concurrency
|
|||
|
|
|
|||
|
|
Five and twenty concurrent requests against an endpoint with a fixed
|
|||
|
|
1-second server-side delay (`httpbin.org/delay/1`), so any serialization
|
|||
|
|
in either client shows up directly as total time approaching `N × 1s`
|
|||
|
|
rather than approaching `1s`:
|
|||
|
|
|
|||
|
|
| Concurrency | stealthii | aiohttp |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 5× | 1.449s – 2.311s across 3 rounds | 1.907s – 3.152s across 3 rounds |
|
|||
|
|
| 20× | 2.033s, 20/20 succeeded | 1.867s, 20/20 succeeded |
|
|||
|
|
|
|||
|
|
Both clients complete 20 concurrent 1-second-delay requests in
|
|||
|
|
~2 seconds total, not 20 seconds — confirming the fast path's requests
|
|||
|
|
against the one shared `BrowserContext` are not serialized through some
|
|||
|
|
hidden single-flight point (the underlying CDP connection, the Python
|
|||
|
|
process, or the browser's own request handling). The 5× round-to-round
|
|||
|
|
spread on both clients is larger than the difference between the two
|
|||
|
|
clients, again pointing at server/network variance rather than a
|
|||
|
|
client-side bottleneck.
|
|||
|
|
|
|||
|
|
## Practical takeaway
|
|||
|
|
|
|||
|
|
For the request volumes and concurrency levels a scraping/search
|
|||
|
|
aggregator workload produces (single digits to low tens of concurrent
|
|||
|
|
requests), the fast path's overhead relative to `aiohttp` is negligible
|
|||
|
|
next to ordinary network latency, and it does not degrade under
|
|||
|
|
concurrency up to at least 20 simultaneous requests on one shared
|
|||
|
|
context. The one meaningfully different cost is the one-time browser
|
|||
|
|
launch (~1s) — pay it once at application startup (`await
|
|||
|
|
stealth.start()` in an `on_startup` hook, or accept it landing on
|
|||
|
|
whichever request happens to be first) rather than per request.
|
|||
|
|
|
|||
|
|
`render()`/`screenshot()`/`page()` (full page navigation) were not
|
|||
|
|
benchmarked here — they are inherently much heavier than the fast path
|
|||
|
|
(loading a real document, executing its JS, subject to `max_tabs`), by
|
|||
|
|
design and by necessity for the targets that require them. Reach for the
|
|||
|
|
fast path by default; escalate to page rendering only for targets that
|
|||
|
|
actually need JS execution.
|
|||
|
|
|
|||
|
|
## Methodology notes
|
|||
|
|
|
|||
|
|
- Measured with Python's `time.monotonic()` around `asyncio.gather()`
|
|||
|
|
batches, one process, one machine, one network path — not a controlled
|
|||
|
|
lab benchmark. Re-running these will not reproduce the exact numbers,
|
|||
|
|
only the same qualitative shape (comparable per-request cost, no
|
|||
|
|
concurrency bottleneck).
|
|||
|
|
- `httpbin.org` is a shared public service; individual requests can be
|
|||
|
|
slow or occasionally rate-limited independent of either client. An
|
|||
|
|
earlier attempt at this same measurement against
|
|||
|
|
`html.duckduckgo.com/html/` was discarded after DuckDuckGo started
|
|||
|
|
returning `202` instead of `200` after a handful of identical rapid
|
|||
|
|
requests — a server-side response to request pattern, not a
|
|||
|
|
client-timing artifact, but one that would have corrupted a latency
|
|||
|
|
comparison.
|