Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9383ac4

Browse filesBrowse 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
Copy full SHA for 9383ac4

2 files changed

+158-49Lines changed: 158 additions & 49 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file
+67Lines 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+
}
Collapse file

‎src/node_http_parser.cc‎

Copy file name to clipboardExpand all lines: src/node_http_parser.cc
+91-49Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.