Skip to content

Conversation

bdraco
Copy link
Member

@bdraco bdraco commented May 24, 2025

continued from #10976 (comment)

Notes

This won't show up any different on codspeed since it excludes syscalls.

Summary

This PR optimizes network efficiency for small HTTP requests and responses by coalescing headers and body data into a single TCP packet when the combined size is small. This reduces the number of packets sent over the network, improving latency and reducing overhead.

Most importantly, this fixes compatibility with memory-constrained IoT devices that can only perform a single read operation and expect HTTP payloads in one packet. This aligns aiohttp with other popular HTTP clients that already coalesce small requests.

Additionally, when client middleware communicates with an aiohttp server, connection reuse is more likely to occur since complete responses arrive in a single packet for small payloads, further improving performance in aiohttp-to-aiohttp communication.

Changes

Core Implementation

  1. Modified StreamWriter in http_writer.py:

    • Added header buffering mechanism with _headers_buf and _headers_written attributes
    • Modified write_headers() to buffer headers instead of sending immediately
    • Added send_headers() method to force sending buffered headers when needed
    • Implemented coalescing logic in write() and write_eof() methods
    • Uses _writelines() for zero-copy writes when coalescing data
    • Improved set_eof() to use single write for headers + chunked EOF marker
    • Optimized condition checks by evaluating boolean drain before buffer size comparison
  2. Updated ClientRequest in client_reqrep.py:

    • Ensures 100-continue support works with header buffering by calling writer.send_headers() before waiting
    • Maintains header buffering for regular requests to enable coalescing
  3. Added Header Buffering Control in web_response.py:

    • Added _send_headers_immediately class attribute (defaults to True for backward compatibility)
    • StreamResponse sends headers immediately (for subclasses like FileResponse that use sendfile)
    • Response class sets _send_headers_immediately = False to opt into header buffering
    • _write_headers() checks this attribute to decide whether to send headers immediately

Benefits

  • Reduced packet count: Small requests/responses now send headers+body in one packet instead of two
  • Lower latency: Fewer packets mean fewer round trips and less overhead
  • Zero-copy optimization: Uses _writelines() to avoid memory copies
  • Fixes IoT device compatibility: Many IoT devices with limited heap memory can only perform a single read operation and expect the complete HTTP response in one packet. This optimization ensures small responses arrive as a single unit, preventing failures on memory-constrained devices
  • Backward compatible: Maintains existing API and behavior, including full support for chunked encoding, compression, and all other features

Technical Details

  • Headers are buffered when body is expected (controlled by _send_headers_immediately attribute)
  • If total size (headers + body) is small, they're sent together using _writelines()
  • Works for both regular and chunked transfer encoding
  • Compression is fully supported with coalescing
  • Special cases (100-continue, WebSocket, sendfile) properly handled with immediate header sending
  • The OS/TCP stack handles the actual packet boundaries efficiently

Backward Compatibility

  • StreamResponse and its subclasses maintain existing behavior (headers sent immediately)
  • Only Response class opts into header buffering for packet coalescing
  • All existing APIs remain unchanged
  • External StreamWriter subclasses continue to work as before

Copy link

codecov bot commented May 24, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.78%. Comparing base (54f1a84) to head (f97c4c7).
⚠️ Report is 476 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10991      +/-   ##
==========================================
+ Coverage   98.77%   98.78%   +0.01%     
==========================================
  Files         129      129              
  Lines       39624    40078     +454     
  Branches     2187     2204      +17     
==========================================
+ Hits        39137    39591     +454     
  Misses        339      339              
  Partials      148      148              
Flag Coverage Δ
CI-GHA 98.66% <100.00%> (+0.01%) ⬆️
OS-Linux 98.38% <100.00%> (+0.01%) ⬆️
OS-Windows 96.58% <100.00%> (+0.03%) ⬆️
OS-macOS 97.54% <100.00%> (+0.03%) ⬆️
Py-3.10.11 97.43% <100.00%> (+0.02%) ⬆️
Py-3.10.17 97.95% <100.00%> (+0.01%) ⬆️
Py-3.11.12 98.04% <100.00%> (+0.01%) ⬆️
Py-3.11.9 97.52% <100.00%> (+0.02%) ⬆️
Py-3.12.10 98.44% <100.00%> (+0.01%) ⬆️
Py-3.13.3 98.42% <100.00%> (+<0.01%) ⬆️
Py-3.9.13 97.32% <100.00%> (+0.03%) ⬆️
Py-3.9.22 97.82% <100.00%> (+0.01%) ⬆️
Py-pypy7.3.16 82.17% <92.12%> (-12.68%) ⬇️
VM-macos 97.54% <100.00%> (+0.03%) ⬆️
VM-ubuntu 98.38% <100.00%> (+0.01%) ⬆️
VM-windows 96.58% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

codspeed-hq bot commented May 24, 2025

CodSpeed Performance Report

Merging #10991 will improve performances by 8.62%

Comparing reduce_syscalls_write (f97c4c7) with master (54f1a84)

Summary

⚡ 1 improvements
✅ 58 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
test_get_request_with_251308_compressed_chunked_payload[isal.isal_zlib-pyloop] 69.7 ms 64.2 ms +8.62%

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label May 24, 2025
@bdraco bdraco marked this pull request as ready for review May 24, 2025 16:45
@bdraco bdraco requested review from asvetlov and webknjaz as code owners May 24, 2025 16:45
@bdraco bdraco added this to the 3.12 milestone May 24, 2025
@bdraco bdraco enabled auto-merge (squash) May 24, 2025 17:04
@bdraco bdraco merged commit 452458a into master May 24, 2025
40 checks passed
@bdraco bdraco deleted the reduce_syscalls_write branch May 24, 2025 17:05
Copy link
Contributor

patchback bot commented May 24, 2025

Backport to 3.12: 💔 cherry-picking failed — conflicts found

❌ Failed to cleanly apply 452458a on top of patchback/backports/3.12/452458a959814ef7bf67860f49606e5ed4649af9/pr-10991

Backporting merged PR #10991 into master

  1. Ensure you have a local repo clone of your fork. Unless you cloned it
    from the upstream, this would be your origin remote.
  2. Make sure you have an upstream repo added as a remote too. In these
    instructions you'll refer to it by the name upstream. If you don't
    have it, here's how you can add it:
    $ git remote add upstream https://github.com/aio-libs/aiohttp.git
  3. Ensure you have the latest copy of upstream and prepare a branch
    that will hold the backported code:
    $ git fetch upstream
    $ git checkout -b patchback/backports/3.12/452458a959814ef7bf67860f49606e5ed4649af9/pr-10991 upstream/3.12
  4. Now, cherry-pick PR Optimize small HTTP requests/responses by coalescing headers and body into a single packet #10991 contents into that branch:
    $ git cherry-pick -x 452458a959814ef7bf67860f49606e5ed4649af9
    If it'll yell at you with something like fatal: Commit 452458a959814ef7bf67860f49606e5ed4649af9 is a merge but no -m option was given., add -m 1 as follows instead:
    $ git cherry-pick -m1 -x 452458a959814ef7bf67860f49606e5ed4649af9
  5. At this point, you'll probably encounter some merge conflicts. You must
    resolve them in to preserve the patch from PR Optimize small HTTP requests/responses by coalescing headers and body into a single packet #10991 as close to the
    original as possible.
  6. Push this branch to your fork on GitHub:
    $ git push origin patchback/backports/3.12/452458a959814ef7bf67860f49606e5ed4649af9/pr-10991
  7. Create a PR, ensure that the CI is green. If it's not — update it so that
    the tests and any other checks pass. This is it!
    Now relax and wait for the maintainers to process your pull request
    when they have some cycles to do reviews. Don't worry — they'll tell you if
    any improvements are necessary when the time comes!

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

bdraco added a commit that referenced this pull request May 24, 2025
… into a single packet (#10991)

(cherry picked from commit 452458a)
bdraco added a commit that referenced this pull request May 24, 2025
…nses by coalescing headers and body into a single packet (#10992)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants