8000 http: implement slab allocation for HTTP header parsing · nodejs/node@9383ac4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9383ac4

Browse files
mertcanaltinaduh95
authored andcommitted
http: implement slab allocation for HTTP header parsing
PR-URL: #61375 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent ade36ac commit 9383ac4

File tree

2 files changed

+158
-49
lines changed

2 files changed

+158
-49
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
const bench = common.createBenchmark(main, {
6+
len: [8, 16],
7+
frags: [2, 4, 8],
8+
n: [1e5],
9+
}, {
10+
flags: ['--expose-internals', '--no-warnings'],
11+
});
12+
13+
function main({ len, frags, n }) {
14+
const { HTTPParser } = common.binding('http_parser');
15+
const REQUEST = HTTPParser.REQUEST;
16+
const kOnHeaders = HTTPParser.kOnHeaders | 0;
17+
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
18+
const kOnBody = HTTPParser.kOnBody | 0;
19+
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
20+
21+
function processHeaderFragmented(fragments, n) {
22+
const parser = newParser(REQUEST);
23+
24+
bench.start();
25+
for (let i = 0; i < n; i++) {
26+
// Send header in fragments
27+
for (const frag of fragments) {
28+
parser.execute(frag, 0, frag.length);
29+
}
30+
parser.initialize(REQUEST, {});
31+
}
32+
bench.end(n);
33+
}
34+
35+
function newParser(type) {
36+
const parser = new HTTPParser();
37+
parser.initialize(type, {});
38+
39+
parser.headers = [];
40+
41+
parser[kOnHeaders] = function() { };
42+
parser[kOnHeadersComplete] = function() { };
43+
parser[kOnBody] = function() { };
44+
parser[kOnMessageComplete] = function() { };
45+
46+
return parser;
47+
}
48+
49+
// Build the header
50+
let header = `GET /hello HTTP/1.1\r\nContent-Type: text/plain\r\n`;
51+
52+
for (let i = 0; i < len; i++) {
53+
header += `X-Filler${i}: ${Math.random().toString(36).substring(2)}\r\n`;
54+
}
55+
header += '\r\n';
56+
57+
// Split header into fragments
58+
const headerBuf = Buffer.from(header);
59+
const fragSize = Math.ceil(headerBuf.length / frags);
60+
const fragments = [];
61+
62+
for (let i = 0; i < headerBuf.length; i += fragSize) {
63+
fragments.push(headerBuf.slice(i, Math.min(i + fragSize, headerBuf.length)));
64+
}
65+
66+
processHeaderFragmented(fragments, n);
67+
}

src/node_http_parser.cc

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -122,72 +122,116 @@ class BindingData : public BaseObject {
122122
SET_MEMORY_INFO_NAME(BindingData)
123123
};
124124

125-
// helper class for the Parser
126-
struct StringPtr {
127-
StringPtr() {
128-
on_heap_ = false;
129-
Reset();
130-
}
125+
class Parser;
126+
127+
class StringPtrAllocator {
128+
public:
129+
// Memory impact: ~8KB per parser (66 StringPtr × 128 bytes).
130+
static constexpr size_t kSlabSize = 8192;
131131

132+
StringPtrAllocator() = default;
132133

133-
~StringPtr() {
134-
Reset();
134+
// Allocate memory from the slab. Returns nullptr if full.
135+
char* TryAllocate(size_t size) {
136+
if (length_ + size > kSlabSize) {
137+
return nullptr;
138+
}
139+
char* ptr = buffer_ + length_;
140+
length_ += size;
141+
return ptr;
142+
}
143+
144+
// Check if pointer is within this allocator's buffer.
145+
bool Contains(const char* ptr) const {
146+
return ptr >= buffer_ && ptr < buffer_ + kSlabSize;
135147
}
136148

149+
// Reset allocator for new message.
150+
void Reset() { length_ = 0; }
137151

138-
// If str_ does not point to a heap string yet, this function makes it do
152+
private:
153+
char buffer_[kSlabSize];
154+
size_t length_ = 0;
155+
};
156+
157+
struct StringPtr {
158+
StringPtr() = default;
159+
~StringPtr() { Reset(); }
160+
161+
StringPtr(const StringPtr&) = delete;
162+
StringPtr& operator=(const StringPtr&) = delete;
163+
164+
// If str_ does not point to owned storage yet, this function makes it do
139165
// so. This is called at the end of each http_parser_execute() so as not
140166
// to leak references. See issue #2438 and test-http-parser-bad-ref.js.
141-
void Save() {
142-
if (!on_heap_ && size_ > 0) {
143-
char* s = new char[size_];
144-
memcpy(s, str_, size_);
145-
str_ = s;
146-
on_heap_ = true;
167+
void Save(StringPtrAllocator* allocator) {
168+
if (str_ == nullptr || on_heap_ ||
169+
(allocator != nullptr && allocator->Contains(str_))) {
170+
return;
147171
}
172+
// Try allocator first, fall back to heap
173+
if (allocator != nullptr) {
174+
char* ptr = allocator->TryAllocate(size_);
175+
if (ptr != nullptr) {
176+
memcpy(ptr, str_, size_);
177+
str_ = ptr;
178+
return;
179+
}
180+
}
181+
char* s = new char[size_];
182+
memcpy(s, str_, size_);
183+
str_ = s;
184+
on_heap_ = true;
148185
}
149186

150-
151187
void Reset() {
152188
if (on_heap_) {
153189
delete[] str_;
154190
on_heap_ = false;
155191
}
156-
157192
str_ = nullptr;
158193
size_ = 0;
159194
}
160195

161-
162-
void Update(const char* str, size_t size) {
196+
void Update(const char* str, size_t size, StringPtrAllocator* allocator) {
163197
if (str_ == nullptr) {
164198
str_ = str;
165-
} else if (on_heap_ || str_ + size_ != str) {
166-
// Non-consecutive input, make a copy on the heap.
167-
// TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad.
168-
char* s = new char[size_ + size];
169-
memcpy(s, str_, size_);
170-
memcpy(s + size_, str, size);
171-
172-
if (on_heap_)
173-
delete[] str_;
174-
else
175-
on_heap_ = true;
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->TryAllocate(new_size);
209+
}
176210

177-
str_ = s;
211+
if (new_str != nullptr) {
212+
memcpy(new_str, str_, size_);
213+
memcpy(new_str + size_, str, size);
214+
str_ = new_str;
215+
} else {
216+
// Fall back to heap
217+
char* s = new char[new_size];
218+
memcpy(s, str_, size_);
219+
memcpy(s + size_, str, size);
220+
if (on_heap_) delete[] str_;
221+
str_ = s;
222+
on_heap_ = true;
223+
}
178224
}
179225
size_ += size;
180226
}
181227

182-
183228
Local<String> ToString(Environment* env) const {
184229
if (size_ != 0)
185230
return OneByteString(env->isolate(), str_, size_);
186231
else
187232
return String::Empty(env->isolate());
188233
}
189234

190-
191235
// Strip trailing OWS (SPC or HTAB) from string.
192236
Local<String> ToTrimmedString(Environment* env) {
193237
while (size_ > 0 && IsOWS(str_[size_ - 1])) {
@@ -196,14 +240,11 @@ struct StringPtr {
196240
return ToString(env);
197241
}
198242

199-
200-
const char* str_;
201-
bool on_heap_;
202-
size_t size_;
243+
const char* str_ = nullptr;
244+
bool on_heap_ = false;
245+
size_t size_ = 0;
203246
};
204247

205-
class Parser;
206-
207248
struct ParserComparator {
208249
bool operator()(const Parser* lhs, const Parser* rhs) const;
209250
};
@@ -259,8 +300,7 @@ class Parser : public AsyncWrap, public StreamListener {
259300
: AsyncWrap(binding_data->env(), wrap),
260301
current_buffer_len_(0),
261302
current_buffer_data_(nullptr),
262-
binding_data_(binding_data) {
263-
}
303+
binding_data_(binding_data) {}
264304

265305
SET_NO_MEMORY_INFO()
266306
SET_MEMORY_INFO_NAME(Parser)
@@ -278,6 +318,7 @@ class Parser : public AsyncWrap, public StreamListener {
278318
headers_completed_ = false;
279319
chunk_extensions_nread_ = 0;
280320
last_message_start_ = uv_hrtime();
321+
allocator_.Reset();
281322
url_.Reset();
282323
status_message_.Reset();
283324

@@ -308,7 +349,7 @@ class Parser : public AsyncWrap, public StreamListener {
308349
return rv;
309350
}
310351

311-
url_.Update(at, length);
352+
url_.Update(at, length, &allocator_);
312353
return 0;
313354
}
314355

@@ -319,7 +360,7 @@ class Parser : public AsyncWrap, public StreamListener {
319360
return rv;
320361
}
321362

322-
status_message_.Update(at, length);
363+
status_message_.Update(at, length, &allocator_);
323364
return 0;
324365
}
325366

@@ -345,7 +386,7 @@ class Parser : public AsyncWrap, public StreamListener {
345386
CHECK_LT(num_fields_, kMaxHeaderFieldsCount);
346387
CHECK_EQ(num_fields_, num_values_ + 1);
347388

348-
fields_[num_fields_ - 1].Update(at, length);
389+
fields_[num_fields_ - 1].Update(at, length, &allocator_);
349390

350391
return 0;
351392
}
@@ -366,7 +407,7 @@ class Parser : public AsyncWrap, public StreamListener {
366407
CHECK_LT(num_values_, arraysize(values_));
367408
CHECK_EQ(num_values_, num_fields_);
368409

369-
values_[num_values_ - 1].Update(at, length);
410+
values_[num_values_ - 1].Update(at, length, &allocator_);
370411

371412
return 0;
372413
}
@@ -594,15 +635,15 @@ class Parser : public AsyncWrap, public StreamListener {
594635
}
595636

596637
void Save() {
597-
url_.Save();
598-
status_message_.Save();
638+
url_.Save(&allocator_);
639+
status_message_.Save(&allocator_);
599640

600641
for (size_t i = 0; i < num_fields_; i++) {
601-
fields_[i].Save();
642+
fields_[i].Save(&allocator_);
602643
}
603644

604645
for (size_t i = 0; i < num_values_; i++) {
605-
values_[i].Save();
646+
values_[i].Save(&allocator_);
606647
}
607648
}
608649

@@ -1006,6 +1047,7 @@ class Parser : public AsyncWrap, public StreamListener {
10061047

10071048

10081049
llhttp_t parser_;
1050+
StringPtrAllocator allocator_; // shared slab for all StringPtrs
10091051
StringPtr fields_[kMaxHeaderFieldsCount]; // header fields
10101052
StringPtr values_[kMaxHeaderFieldsCount]; // header values
10111053
StringPtr url_;

0 commit comments

Comments
 (0)
0