From 94df027c8a6acda26530269f4b1c2cee68d74023 Mon Sep 17 00:00:00 2001 From: John Mallery Date: Mon, 30 Mar 2026 20:10:28 +0200 Subject: [PATCH] Add write-data-frame-region for zero-copy DATA frame sending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit write-data-frame-region accepts a source buffer with offset and length instead of a whole octet vector. This eliminates an intermediate subseq allocation when sending large responses as multiple DATA frames. Use case: when splitting a 64KB response buffer into four 16KB DATA frames, each frame previously required: 1. (subseq buffer start (+ start 16384)) — 16KB alloc + copy 2. write-frame's replace into frame buffer — 16KB copy With write-data-frame-region, only step 2 remains (single copy from the source region directly into the frame buffer). Tested in CL-HTTP's HTTP/2 server on SBCL: 75 MB/s throughput for 10MB files, 1000 concurrent requests with zero failures. --- core/frames/data.lisp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/frames/data.lisp b/core/frames/data.lisp index 60b4740..a1dbe46 100644 --- a/core/frames/data.lisp +++ b/core/frames/data.lisp @@ -498,6 +498,36 @@ As always, use untrace to stop tracing." (incf start (length datum)))) data))) +(defun write-data-frame-region (stream source source-offset payload-length + &key padded end-stream) + "Write a DATA frame from SOURCE[SOURCE-OFFSET..SOURCE-OFFSET+PAYLOAD-LENGTH). +Like WRITE-DATA-FRAME but copies only the specified region into the frame +buffer, avoiding an intermediate SUBSEQ allocation when sending a portion of +a larger buffer as a single DATA frame." + (declare (type (simple-array (unsigned-byte 8) (*)) source) + (type fixnum source-offset payload-length) + (optimize (speed 3))) + (let* ((padded-length (padded-length payload-length padded)) + (buffer (make-octet-buffer (+ 9 padded-length))) + (keys (list :end-stream end-stream))) + (declare (dynamic-extent keys)) + (write-frame-header-to-vector buffer 0 padded-length +data-frame+ + (flags-to-code keys) + (get-stream-id stream) nil) + (let ((frame-start (cond (padded (setf (aref buffer 9) (length padded)) 10) + (t 9)))) + (replace buffer source :start1 frame-start + :start2 source-offset + :end2 (the fixnum (+ source-offset payload-length))) + (when padded + (replace buffer padded :start1 (- (length buffer) (length padded))))) + (account-write-window-contribution (get-connection stream) + stream payload-length) + (queue-frame (get-connection stream) buffer) + (when end-stream + (change-state-on-write-end stream)) + buffer)) + (define-frame-writer 8 write-window-update-frame stream-or-connection nil 4 ((window-size-increment 31)) ((reserved t)) "``` +-+-------------------------------------------------------------+