Skip to content

Commit 0cc64e0

Browse files
committed
http: use intrusive lists in ConnectionsList
Every HTTP message was performing multiple erase and insert operations on two std::set instances ordered by a mutating key, showing up as ~2% of CPU cycles in a hello-world server profile due to red-black tree rebalancing and node allocations. Replace both sets with intrusive doubly-linked lists. Membership in the list of all connections no longer changes per message, and updating the active connections list is now O(1) with no allocations. Appending to the tail keeps the active list ordered by last_message_start_ because uv_hrtime() is monotonic. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 347e266 commit 0cc64e0

1 file changed

Lines changed: 82 additions & 71 deletions

File tree

‎src/node_http_parser.cc‎

Lines changed: 82 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -246,55 +246,63 @@ struct StringPtr {
246246
size_t size_ = 0;
247247
};
248248

249-
struct ParserComparator {
250-
bool operator()(const Parser* lhs, const Parser* rhs) const;
249+
// Intrusive doubly-linked list node, linked to itself when not in a list.
250+
struct ParserListNode {
251+
ParserListNode* prev = this;
252+
ParserListNode* next = this;
253+
254+
ParserListNode() = default;
255+
~ParserListNode() { Remove(); }
256+
257+
ParserListNode(const ParserListNode&) = delete;
258+
ParserListNode& operator=(const ParserListNode&) = delete;
259+
260+
void Remove() {
261+
prev->next = next;
262+
next->prev = prev;
263+
prev = this;
264+
next = this;
265+
}
251266
};
252267

253268
class ConnectionsList : public BaseObject {
254269
public:
255-
static void New(const FunctionCallbackInfo<Value>& args);
270+
static void New(const FunctionCallbackInfo<Value>& args);
256271

257-
static void All(const FunctionCallbackInfo<Value>& args);
272+
static void All(const FunctionCallbackInfo<Value>& args);
258273

259-
static void Idle(const FunctionCallbackInfo<Value>& args);
274+
static void Idle(const FunctionCallbackInfo<Value>& args);
260275

261-
static void Active(const FunctionCallbackInfo<Value>& args);
276+
static void Active(const FunctionCallbackInfo<Value>& args);
262277

263-
static void Expired(const FunctionCallbackInfo<Value>& args);
278+
static void Expired(const FunctionCallbackInfo<Value>& args);
264279

265-
void Push(Parser* parser) {
266-
all_connections_.insert(parser);
267-
}
280+
inline void Push(Parser* parser);
268281

269-
void Pop(Parser* parser) {
270-
all_connections_.erase(parser);
271-
}
282+
inline void Pop(Parser* parser);
272283

273-
void PushActive(Parser* parser) {
274-
active_connections_.insert(parser);
275-
}
284+
inline void PushActive(Parser* parser);
276285

277-
void PopActive(Parser* parser) {
278-
active_connections_.erase(parser);
279-
}
286+
inline void PopActive(Parser* parser);
280287

281-
SET_NO_MEMORY_INFO()
282-
SET_MEMORY_INFO_NAME(ConnectionsList)
283-
SET_SELF_SIZE(ConnectionsList)
288+
SET_NO_MEMORY_INFO()
289+
SET_MEMORY_INFO_NAME(ConnectionsList)
290+
SET_SELF_SIZE(ConnectionsList)
284291

285292
private:
286-
ConnectionsList(Environment* env, Local<Object> object)
293+
ConnectionsList(Environment* env, Local<Object> object)
287294
: BaseObject(env, object) {
288-
MakeWeak();
289-
}
295+
MakeWeak();
296+
}
290297

291-
std::set<Parser*, ParserComparator> all_connections_;
292-
std::set<Parser*, ParserComparator> active_connections_;
298+
// active_connections_ is ordered by last_message_start_, as parsers are
299+
// appended right after it is assigned from the monotonic uv_hrtime().
300+
ParserListNode all_connections_;
301+
ParserListNode active_connections_;
293302
};
294303

295304
class Parser : public AsyncWrap, public StreamListener {
296305
friend class ConnectionsList;
297-
friend struct ParserComparator;
298306

299307
public:
300308
Parser(BindingData* binding_data, Local<Object> wrap)
@@ -308,13 +316,6 @@ class Parser : public AsyncWrap, public StreamListener {
308316
SET_SELF_SIZE(Parser)
309317

310318
int on_message_begin() {
311-
// Important: Pop from the lists BEFORE resetting the last_message_start_
312-
// otherwise std::set.erase will fail.
313-
if (connectionsList_ != nullptr) {
314-
connectionsList_->Pop(this);
315-
connectionsList_->PopActive(this);
316-
}
317-
318319
num_fields_ = num_values_ = 0;
319320
headers_completed_ = false;
320321
chunk_extensions_nread_ = 0;
@@ -325,7 +326,6 @@ class Parser : public AsyncWrap, public StreamListener {
325326
status_message_.Reset();
326327

327328
if (connectionsList_ != nullptr) {
328-
connectionsList_->Push(this);
329329
connectionsList_->PushActive(this);
330330
}
331331

@@ -344,7 +344,6 @@ class Parser : public AsyncWrap, public StreamListener {
344344
return 0;
345345
}
346346

347-
348347
int on_url(const char* at, size_t length) {
349348
int rv = TrackHeader(length);
350349
if (rv != 0) {
@@ -542,19 +541,12 @@ class Parser : public AsyncWrap, public StreamListener {
542541
int on_message_complete() {
543542
HandleScope scope(env()->isolate());
544543

545-
// Important: Pop from the lists BEFORE resetting the last_message_start_
546-
// otherwise std::set.erase will fail.
547544
if (connectionsList_ != nullptr) {
548-
connectionsList_->Pop(this);
549545
connectionsList_->PopActive(this);
550546
}
551547

552548
last_message_start_ = 0;
553549

554-
if (connectionsList_ != nullptr) {
555-
connectionsList_->Push(this);
556-
}
557-
558550
if (num_fields_)
559551
Flush(); // Flush trailing HTTP headers.
560552

@@ -740,8 +732,6 @@ class Parser : public AsyncWrap, public StreamListener {
740732
// server.timeout is left to the default value of zero.
741733
parser->last_message_start_ = uv_hrtime();
742734

743-
// Important: Push into the lists AFTER setting the last_message_start_
744-
// otherwise std::set.erase will fail later.
745735
parser->connectionsList_->Push(parser);
746736
parser->connectionsList_->PushActive(parser);
747737
} else {
@@ -1116,6 +1106,8 @@ class Parser : public AsyncWrap, public StreamListener {
11161106
uint64_t max_http_header_size_;
11171107
uint64_t last_message_start_;
11181108
ConnectionsList* connectionsList_;
1109+
ParserListNode all_node_;
1110+
ParserListNode active_node_;
11191111

11201112
BaseObjectPtr<BindingData> binding_data_;
11211113

@@ -1143,18 +1135,34 @@ class Parser : public AsyncWrap, public StreamListener {
11431135
static const llhttp_settings_t settings;
11441136
};
11451137

1146-
bool ParserComparator::operator()(const Parser* lhs, const Parser* rhs) const {
1147-
if (lhs->last_message_start_ == 0 && rhs->last_message_start_ == 0) {
1148-
// When both parsers are idle, guarantee strict order by
1149-
// comparing pointers as ints.
1150-
return lhs < rhs;
1151-
} else if (lhs->last_message_start_ == 0) {
1152-
return true;
1153-
} else if (rhs->last_message_start_ == 0) {
1154-
return false;
1155-
}
1138+
namespace {
1139+
1140+
// Append `node` at the tail of the list headed by `head`, unlinking it from
1141+
// any list it is currently in.
1142+
void ListPushBack(ParserListNode* head, ParserListNode* node) {
1143+
node->Remove();
1144+
node->prev = head->prev;
1145+
node->next = head;
1146+
head->prev->next = node;
1147+
head->prev = node;
1148+
}
1149+
1150+
} // anonymous namespace
11561151

1157-
return lhs->last_message_start_ < rhs->last_message_start_;
1152+
void ConnectionsList::Push(Parser* parser) {
1153+
ListPushBack(&all_connections_, &parser->all_node_);
1154+
}
1155+
1156+
void ConnectionsList::Pop(Parser* parser) {
1157+
parser->all_node_.Remove();
1158+
}
1159+
1160+
void ConnectionsList::PushActive(Parser* parser) {
1161+
ListPushBack(&active_connections_, &parser->active_node_);
1162+
}
1163+
1164+
void ConnectionsList::PopActive(Parser* parser) {
1165+
parser->active_node_.Remove();
11581166
}
11591167

11601168
void ConnectionsList::New(const FunctionCallbackInfo<Value>& args) {
@@ -1172,8 +1180,10 @@ void ConnectionsList::All(const FunctionCallbackInfo<Value>& args) {
11721180
ASSIGN_OR_RETURN_UNWRAP(&list, args.This());
11731181

11741182
LocalVector<Value> result(isolate);
1175-
result.reserve(list->all_connections_.size());
1176-
for (auto parser : list->all_connections_) {
1183+
for (ParserListNode* node = list->all_connections_.next;
1184+
node != &list->all_connections_;
1185+
node = node->next) {
1186+
Parser* parser = ContainerOf(&Parser::all_node_, node);
11771187
result.emplace_back(parser->object());
11781188
}
11791189

@@ -1189,8 +1199,10 @@ void ConnectionsList::Idle(const FunctionCallbackInfo<Value>& args) {
11891199
ASSIGN_OR_RETURN_UNWRAP(&list, args.This());
11901200

11911201
LocalVector<Value> result(isolate);
1192-
result.reserve(list->all_connections_.size());
1193-
for (auto parser : list->all_connections_) {
1202+
for (ParserListNode* node = list->all_connections_.next;
1203+
node != &list->all_connections_;
1204+
node = node->next) {
1205+
Parser* parser = ContainerOf(&Parser::all_node_, node);
11941206
if (parser->last_message_start_ == 0 || !parser->received_data_) {
11951207
result.emplace_back(parser->object());
11961208
}
@@ -1208,8 +1220,10 @@ void ConnectionsList::Active(const FunctionCallbackInfo<Value>& args) {
12081220
ASSIGN_OR_RETURN_UNWRAP(&list, args.This());
12091221

12101222
LocalVector<Value> result(isolate);
1211-
result.reserve(list->active_connections_.size());
1212-
for (auto parser : list->active_connections_) {
1223+
for (ParserListNode* node = list->active_connections_.next;
1224+
node != &list->active_connections_;
1225+
node = node->next) {
1226+
Parser* parser = ContainerOf(&Parser::active_node_, node);
12131227
result.emplace_back(parser->object());
12141228
}
12151229

@@ -1253,14 +1267,11 @@ void ConnectionsList::Expired(const FunctionCallbackInfo<Value>& args) {
12531267
return args.GetReturnValue().Set(Array::New(isolate, 0));
12541268
}
12551269

1256-
auto iter = list->active_connections_.begin();
1257-
auto end = list->active_connections_.end();
1258-
12591270
LocalVector<Value> result(isolate);
1260-
result.reserve(list->active_connections_.size());
1261-
while (iter != end) {
1262-
Parser* parser = *iter;
1263-
iter++;
1271+
ParserListNode* node = list->active_connections_.next;
1272+
while (node != &list->active_connections_) {
1273+
Parser* parser = ContainerOf(&Parser::active_node_, node);
1274+
node = node->next;
12641275

12651276
// Check for expiration.
12661277
if (
@@ -1272,7 +1283,7 @@ void ConnectionsList::Expired(const FunctionCallbackInfo<Value>& args) {
12721283
) {
12731284
result.emplace_back(parser->object());
12741285

1275-
list->active_connections_.erase(parser);
1286+
parser->active_node_.Remove();
12761287
}
12771288
}
12781289

0 commit comments

Comments
 (0)