Skip to content

Commit 56393d3

Browse files
committed
implement shared slab allocation
1 parent 5def150 commit 56393d3

File tree

1 file changed

+76
-32
lines changed

1 file changed

+76
-32
lines changed

src/node_http_parser.cc

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -122,66 +122,104 @@ class BindingData : public BaseObject {
122122
SET_MEMORY_INFO_NAME(BindingData)
123123
};
124124

125-
// helper class for the Parser
126-
struct StringPtr {
125+
class Parser;
126+
127+
class StringPtrAllocator {
128+
public:
127129
// Memory impact: ~8KB per parser (66 StringPtr × 128 bytes).
128-
static constexpr size_t kSlabSize = 128;
130+
static constexpr size_t kSlabSize = 8192;
131+
132+
StringPtrAllocator() { buffer_.SetLength(0); }
133+
134+
// Allocate memory from the slab. Returns nullptr if full.
135+
char* Allocate(size_t size) {
136+
const size_t current = buffer_.length();
137+
if (current + size > kSlabSize) {
138+
return nullptr;
139+
}
140+
buffer_.SetLength(current + size);
141+
return buffer_.out() + current;
142+
}
143+
144+
// Check if pointer is within this allocator's buffer.
145+
bool Contains(const char* ptr) const {
146+
return ptr >= buffer_.out() && ptr < buffer_.out() + buffer_.capacity();
147+
}
148+
// Reset allocator for new message.
149+
void Reset() { buffer_.SetLength(0); }
129150

151+
private:
152+
MaybeStackBuffer<char, kSlabSize> buffer_;
153+
};
154+
155+
struct StringPtr {
130156
StringPtr() = default;
131157
~StringPtr() { Reset(); }
132158

133159
StringPtr(const StringPtr&) = delete;
134160
StringPtr& operator=(const StringPtr&) = delete;
135161

136-
// If str_ does not point to a heap string yet, this function makes it do
162+
void SetAllocator(StringPtrAllocator* allocator) { allocator_ = allocator; }
163+
164+
// If str_ does not point to owned storage yet, this function makes it do
137165
// so. This is called at the end of each http_parser_execute() so as not
138166
// to leak references. See issue #2438 and test-http-parser-bad-ref.js.
139167
void Save() {
140-
if (!on_heap_ && !using_slab_ && size_ > 0) {
141-
if (size_ <= kSlabSize) {
142-
memcpy(slab_, str_, size_);
143-
str_ = slab_;
144-
using_slab_ = true;
145-
} else {
146-
char* s = new char[size_];
147-
memcpy(s, str_, size_);
148-
str_ = s;
149-
on_heap_ = true;
168+
if (str_ == nullptr || on_heap_ ||
169+
(allocator_ != nullptr && allocator_->Contains(str_))) {
170+
return;
171+
}
172+
// Try allocator first, fall back to heap
173+
if (allocator_ != nullptr) {
174+
char* ptr = allocator_->Allocate(size_);
175+
if (ptr != nullptr) {
176+
memcpy(ptr, str_, size_);
177+
str_ = ptr;
178+
return;
150179
}
151180
}
181+
char* s = new char[size_];
182+
memcpy(s, str_, size_);
183+
str_ = s;
184+
on_heap_ = true;
152185
}
153186

154187
void Reset() {
155188
if (on_heap_) {
156189
delete[] str_;
157190
on_heap_ = false;
158191
}
159-
using_slab_ = false;
160192
str_ = nullptr;
161193
size_ = 0;
162194
}
163195

164196
void Update(const char* str, size_t size) {
165197
if (str_ == nullptr) {
166198
str_ = str;
167-
} else if (on_heap_ || using_slab_ || str_ + size_ != str) {
168-
const size_t total = size_ + size;
169-
170-
if (!on_heap_ && total <= kSlabSize) {
171-
if (!using_slab_) {
172-
memcpy(slab_, str_, size_);
173-
using_slab_ = true;
174-
}
175-
memcpy(slab_ + size_, str, size);
176-
str_ = slab_;
199+
} else if (on_heap_ ||
200+
(allocator_ != nullptr && allocator_->Contains(str_)) ||
201+
str_ + size_ != str) {
202+
// Non-consecutive input, make a copy
203+
const size_t new_size = size_ + size;
204+
char* new_str = nullptr;
205+
206+
// Try allocator first (if not already on heap)
207+
if (!on_heap_ && allocator_ != nullptr) {
208+
new_str = allocator_->Allocate(new_size);
209+
}
210+
211+
if (new_str != nullptr) {
212+
memcpy(new_str, str_, size_);
213+
memcpy(new_str + size_, str, size);
214+
str_ = new_str;
177215
} else {
178-
char* s = new char[total];
216+
// Fall back to heap
217+
char* s = new char[new_size];
179218
memcpy(s, str_, size_);
180219
memcpy(s + size_, str, size);
181220
if (on_heap_) delete[] str_;
182-
on_heap_ = true;
183-
using_slab_ = false;
184221
str_ = s;
222+
on_heap_ = true;
185223
}
186224
}
187225
size_ += size;
@@ -204,13 +242,10 @@ struct StringPtr {
204242

205243
const char* str_ = nullptr;
206244
bool on_heap_ = false;
207-
bool using_slab_ = false;
208245
size_t size_ = 0;
209-
char slab_[kSlabSize];
246+
StringPtrAllocator* allocator_ = nullptr;
210247
};
211248

212-
class Parser;
213-
214249
struct ParserComparator {
215250
bool operator()(const Parser* lhs, const Parser* rhs) const;
216251
};
@@ -267,6 +302,13 @@ class Parser : public AsyncWrap, public StreamListener {
267302
current_buffer_len_(0),
268303
current_buffer_data_(nullptr),
269304
binding_data_(binding_data) {
305+
// Wire up all StringPtrs to use the shared allocator
306+
for (size_t i = 0; i < kMaxHeaderFieldsCount; i++) {
307+
fields_[i].SetAllocator(&allocator_);
308+
values_[i].SetAllocator(&allocator_);
309+
}
310+
url_.SetAllocator(&allocator_);
311+
status_message_.SetAllocator(&allocator_);
270312
}
271313

272314
SET_NO_MEMORY_INFO()
@@ -285,6 +327,7 @@ class Parser : public AsyncWrap, public StreamListener {
285327
headers_completed_ = false;
286328
chunk_extensions_nread_ = 0;
287329
last_message_start_ = uv_hrtime();
330+
allocator_.Reset();
288331
url_.Reset();
289332
status_message_.Reset();
290333

@@ -1013,6 +1056,7 @@ class Parser : public AsyncWrap, public StreamListener {
10131056

10141057

10151058
llhttp_t parser_;
1059+
StringPtrAllocator allocator_; // shared slab for all StringPtrs
10161060
StringPtr fields_[kMaxHeaderFieldsCount]; // header fields
10171061
StringPtr values_[kMaxHeaderFieldsCount]; // header values
10181062
StringPtr url_;

0 commit comments

Comments
 (0)