Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 41 additions & 34 deletions src/node_http_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,70 +124,76 @@ 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).
Comment thread
anonrig marked this conversation as resolved.
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() {
Comment thread
mertcanaltin marked this conversation as resolved.
Outdated
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.
Comment thread
addaleax marked this conversation as resolved.
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<String> ToString(Environment* env) const {
if (size_ != 0)
return OneByteString(env->isolate(), str_, size_);
else
return String::Empty(env->isolate());
}


// Strip trailing OWS (SPC or HTAB) from string.
Local<String> ToTrimmedString(Environment* env) {
while (size_ > 0 && IsOWS(str_[size_ - 1])) {
Expand All @@ -196,10 +202,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];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overall seems like we're re-inventing MaybeStackBuffer here – I think we could just re-use that?

Copy link
Copy Markdown
Member Author

@mertcanaltin mertcanaltin Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! , I've refactored this to use MaybeStackBuffer as the backing store for a allocator.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new benchmark result :

asis (main):

./out/Release/node benchmark/http/bench-parser.js

http/bench-parser.js n=100000 len=4: 1,728,678.1850683796
http/bench-parser.js n=100000 len=8: 1,395,311.5272280464
http/bench-parser.js n=100000 len=16: 981,492.7277523204
http/bench-parser.js n=100000 len=32: 641,907.2347759664

./out/Release/node benchmark/http/bench-parser-fragmented.js

http/bench-parser-fragmented.js n=100000 frags=2 len=8: 1,092,175.0170709684
http/bench-parser-fragmented.js n=100000 frags=4 len=8: 883,046.0674095292
http/bench-parser-fragmented.js n=100000 frags=8 len=8: 772,164.5013385202
http/bench-parser-fragmented.js n=100000 frags=2 len=16: 766,964.5365185371
http/bench-parser-fragmented.js n=100000 frags=4 len=16: 640,379.4448008832
http/bench-parser-fragmented.js n=100000 frags=8 len=16: 520,572.0305757982

new:

./out/Release/node benchmark/http/bench-parser.js

http/bench-parser.js n=100000 len=4: 1,764,556.6527588428
http/bench-parser.js n=100000 len=8: 1,500,760.6980788389
http/bench-parser.js n=100000 len=16: 1,095,013.3922327897
http/bench-parser.js n=100000 len=32: 646,285.1635436564

./out/Release/node benchmark/http/bench-parser-fragmented.js

http/bench-parser-fragmented.js n=100000 frags=2 len=8: 1,361,774.7618279774
http/bench-parser-fragmented.js n=100000 frags=4 len=8: 1,263,148.5873261988
http/bench-parser-fragmented.js n=100000 frags=8 len=8: 1,056,199.0301452405
http/bench-parser-fragmented.js n=100000 frags=2 len=16: 1,021,757.915898309
http/bench-parser-fragmented.js n=100000 frags=4 len=16: 923,235.2934387976
http/bench-parser-fragmented.js n=100000 frags=8 len=16: 826,609.3826364499

};

class Parser;
Expand Down
Loading