From 20bde47f5ce9416e303d21f31e9bf143a4fe33d7 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Tue, 13 Jan 2026 22:59:35 +0300 Subject: [PATCH 1/3] http: implement slab allocation for HTTP header parsing --- src/node_http_parser.cc | 76 +++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 26ddbf57854672..1b51f36ad06e1c 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -124,62 +124,70 @@ class BindingData : public BaseObject { // helper class for the Parser struct StringPtr { - StringPtr() { - on_heap_ = false; - Reset(); - } - + // Memory impact: ~8KB per parser (66 StringPtr × 128 bytes). + static constexpr size_t kSlabSize = 128; - ~StringPtr() { - Reset(); - } + StringPtr() = default; + ~StringPtr() { Reset(); } + StringPtr(const StringPtr&) = delete; + StringPtr& operator=(const StringPtr&) = delete; // If str_ does not point to a heap string yet, this function makes it do // so. This is called at the end of each http_parser_execute() so as not // to leak references. See issue #2438 and test-http-parser-bad-ref.js. void Save() { - if (!on_heap_ && size_ > 0) { - char* s = new char[size_]; - memcpy(s, str_, size_); - str_ = s; - on_heap_ = true; + if (!on_heap_ && !using_slab_ && size_ > 0) { + if (size_ <= kSlabSize) { + memcpy(slab_, str_, size_); + str_ = slab_; + using_slab_ = true; + } else { + char* s = new char[size_]; + memcpy(s, str_, size_); + str_ = s; + on_heap_ = true; + } } } - void Reset() { if (on_heap_) { delete[] str_; on_heap_ = false; } - + using_slab_ = false; str_ = nullptr; size_ = 0; } - void Update(const char* str, size_t size) { if (str_ == nullptr) { str_ = str; - } else if (on_heap_ || str_ + size_ != str) { - // Non-consecutive input, make a copy on the heap. - // TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad. - char* s = new char[size_ + size]; - memcpy(s, str_, size_); - memcpy(s + size_, str, size); - - if (on_heap_) - delete[] str_; - else + } else if (on_heap_ || using_slab_ || str_ + size_ != str) { + const size_t total = size_ + size; + + if (!on_heap_ && total <= kSlabSize) { + if (!using_slab_) { + memcpy(slab_, str_, size_); + using_slab_ = true; + } + memcpy(slab_ + size_, str, size); + str_ = slab_; + } else { + char* s = new char[total]; + memcpy(s, str_, size_); + memcpy(s + size_, str, size); + if (on_heap_) + delete[] str_; on_heap_ = true; - - str_ = s; + using_slab_ = false; + str_ = s; + } } size_ += size; } - Local ToString(Environment* env) const { if (size_ != 0) return OneByteString(env->isolate(), str_, size_); @@ -187,7 +195,6 @@ struct StringPtr { return String::Empty(env->isolate()); } - // Strip trailing OWS (SPC or HTAB) from string. Local ToTrimmedString(Environment* env) { while (size_ > 0 && IsOWS(str_[size_ - 1])) { @@ -196,10 +203,11 @@ struct StringPtr { return ToString(env); } - - const char* str_; - bool on_heap_; - size_t size_; + const char* str_ = nullptr; + bool on_heap_ = false; + bool using_slab_ = false; + size_t size_ = 0; + char slab_[kSlabSize]; }; class Parser; From 91155569ed4f6127a9f582f50c0b953e5b3b89f7 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Tue, 13 Jan 2026 23:06:00 +0300 Subject: [PATCH 2/3] lint --- src/node_http_parser.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 1b51f36ad06e1c..97c31b4d178d52 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -178,8 +178,7 @@ struct StringPtr { char* s = new char[total]; memcpy(s, str_, size_); memcpy(s + size_, str, size); - if (on_heap_) - delete[] str_; + if (on_heap_) delete[] str_; on_heap_ = true; using_slab_ = false; str_ = s; From 5def1500fd6edb0da3657a1061fa814249f08af2 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Thu, 15 Jan 2026 21:13:41 +0300 Subject: [PATCH 3/3] benchmark: add fragmented HTTP header parsing benchmark --- benchmark/http/bench-parser-fragmented.js | 67 +++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 benchmark/http/bench-parser-fragmented.js diff --git a/benchmark/http/bench-parser-fragmented.js b/benchmark/http/bench-parser-fragmented.js new file mode 100644 index 00000000000000..6dd8f332a7ffad --- /dev/null +++ b/benchmark/http/bench-parser-fragmented.js @@ -0,0 +1,67 @@ +'use strict'; + +const common = require('../common'); + +const bench = common.createBenchmark(main, { + len: [8, 16], + frags: [2, 4, 8], + n: [1e5], +}, { + flags: ['--expose-internals', '--no-warnings'], +}); + +function main({ len, frags, n }) { + const { HTTPParser } = common.binding('http_parser'); + const REQUEST = HTTPParser.REQUEST; + const kOnHeaders = HTTPParser.kOnHeaders | 0; + const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; + const kOnBody = HTTPParser.kOnBody | 0; + const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; + + function processHeaderFragmented(fragments, n) { + const parser = newParser(REQUEST); + + bench.start(); + for (let i = 0; i < n; i++) { + // Send header in fragments + for (const frag of fragments) { + parser.execute(frag, 0, frag.length); + } + parser.initialize(REQUEST, {}); + } + bench.end(n); + } + + function newParser(type) { + const parser = new HTTPParser(); + parser.initialize(type, {}); + + parser.headers = []; + + parser[kOnHeaders] = function() { }; + parser[kOnHeadersComplete] = function() { }; + parser[kOnBody] = function() { }; + parser[kOnMessageComplete] = function() { }; + + return parser; + } + + // Build the header + let header = `GET /hello HTTP/1.1\r\nContent-Type: text/plain\r\n`; + + for (let i = 0; i < len; i++) { + header += `X-Filler${i}: ${Math.random().toString(36).substring(2)}\r\n`; + } + header += '\r\n'; + + // Split header into fragments + const headerBuf = Buffer.from(header); + const fragSize = Math.ceil(headerBuf.length / frags); + const fragments = []; + + for (let i = 0; i < headerBuf.length; i += fragSize) { + fragments.push(headerBuf.slice(i, Math.min(i + fragSize, headerBuf.length))); + } + + processHeaderFragmented(fragments, n); +}