src: Avoid to call costly evbuffer_add() repeatedly

The profiler and benchmarking showed that calling evbuffer_add()
repeatedly is very costly. To avoid this, we buffer up small writes
into one large chunk and call evbuffer_add() less times.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-03-03 21:18:24 +09:00
parent 58485bd1d8
commit e34b8ac7fb
8 changed files with 189 additions and 50 deletions

View File

@ -332,10 +332,12 @@ int Http2Handler::on_read()
int Http2Handler::on_write() int Http2Handler::on_write()
{ {
int rv; int rv;
uint8_t buf[4096];
size_t buflen = 0;
auto output = bufferevent_get_output(bev_); auto output = bufferevent_get_output(bev_);
for(;;) { for(;;) {
if(evbuffer_get_length(output) > if(evbuffer_get_length(output) + buflen >
sessions_->get_config()->output_upper_thres) { sessions_->get_config()->output_upper_thres) {
break; break;
} }
@ -351,11 +353,29 @@ int Http2Handler::on_write()
if(datalen == 0) { if(datalen == 0) {
break; break;
} }
if(buflen + datalen > sizeof(buf)) {
rv = evbuffer_add(output, buf, buflen);
if(rv != 0) {
std::cerr << "evbuffer_add() failed" << std::endl;
return -1;
}
buflen = 0;
if(datalen > static_cast<ssize_t>(sizeof(buf))) {
rv = evbuffer_add(output, data, datalen); rv = evbuffer_add(output, data, datalen);
if(rv != 0) { if(rv != 0) {
std::cerr << "evbuffer_add() failed" << std::endl; std::cerr << "evbuffer_add() failed" << std::endl;
return -1; return -1;
} }
continue;
}
}
memcpy(buf + buflen, data, datalen);
buflen += datalen;
}
rv = evbuffer_add(output, buf, buflen);
if(rv != 0) {
std::cerr << "evbuffer_add() failed" << std::endl;
return -1;
} }
if(nghttp2_session_want_read(session_) == 0 && if(nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 && nghttp2_session_want_write(session_) == 0 &&

View File

@ -162,6 +162,8 @@ ssize_t Http2Session::on_read()
int Http2Session::on_write() int Http2Session::on_write()
{ {
int rv; int rv;
uint8_t buf[4096];
size_t buflen = 0;
auto output = bufferevent_get_output(client_->bev); auto output = bufferevent_get_output(client_->bev);
for(;;) { for(;;) {
const uint8_t *data; const uint8_t *data;
@ -173,11 +175,26 @@ int Http2Session::on_write()
if(datalen == 0) { if(datalen == 0) {
break; break;
} }
if(buflen + datalen > sizeof(buf)) {
rv = evbuffer_add(output, buf, buflen);
if(rv == -1) {
return -1;
}
buflen = 0;
if(datalen > static_cast<ssize_t>(sizeof(buf))) {
rv = evbuffer_add(output, data, datalen); rv = evbuffer_add(output, data, datalen);
if(rv == -1) { if(rv == -1) {
return -1; return -1;
} }
} }
}
memcpy(buf + buflen, data, datalen);
buflen += datalen;
}
rv = evbuffer_add(output, buf, buflen);
if(rv == -1) {
return -1;
}
if(nghttp2_session_want_read(session_) == 0 && if(nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 && nghttp2_session_want_write(session_) == 0 &&
evbuffer_get_length(output) == 0) { evbuffer_get_length(output) == 0) {

View File

@ -101,8 +101,29 @@ ssize_t send_callback(spdylay_session *session,
void *user_data) void *user_data)
{ {
auto client = static_cast<Client*>(user_data); auto client = static_cast<Client*>(user_data);
auto spdy_session = static_cast<SpdySession*>(client->session.get());
auto output = bufferevent_get_output(client->bev); auto output = bufferevent_get_output(client->bev);
evbuffer_add(output, data, length); int rv;
if(spdy_session->sendbuflen + length > spdy_session->sendbufmax) {
rv = evbuffer_add(output, spdy_session->sendbuf, spdy_session->sendbuflen);
if(rv == -1) {
return SPDYLAY_ERR_CALLBACK_FAILURE;
}
spdy_session->sendbuflen = 0;
if(length > spdy_session->sendbufmax) {
rv = evbuffer_add(output, data, length);
if(rv == -1) {
return SPDYLAY_ERR_CALLBACK_FAILURE;
}
} else {
memcpy(spdy_session->sendbuf + spdy_session->sendbuflen, data, length);
spdy_session->sendbuflen += length;
}
} else {
memcpy(spdy_session->sendbuf + spdy_session->sendbuflen, data, length);
spdy_session->sendbuflen += length;
}
return length; return length;
} }
} //namespace } //namespace
@ -150,10 +171,22 @@ ssize_t SpdySession::on_read()
int SpdySession::on_write() int SpdySession::on_write()
{ {
int rv; int rv;
uint8_t buf[4096];
sendbuf = buf;
sendbuflen = 0;
sendbufmax = sizeof(buf);
rv = spdylay_session_send(session_); rv = spdylay_session_send(session_);
if(rv != 0) { if(rv != 0) {
return -1; return -1;
} }
rv = bufferevent_write(client_->bev, sendbuf, sendbuflen);
if(rv == -1) {
return -1;
}
if(spdylay_session_want_read(session_) == 0 && if(spdylay_session_want_read(session_) == 0 &&
spdylay_session_want_write(session_) == 0 && spdylay_session_want_write(session_) == 0 &&
evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) { evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) {

View File

@ -42,6 +42,10 @@ public:
virtual ssize_t on_read(); virtual ssize_t on_read();
virtual int on_write(); virtual int on_write();
virtual void terminate(); virtual void terminate();
uint8_t *sendbuf;
size_t sendbuflen;
size_t sendbufmax;
private: private:
Client *client_; Client *client_;
spdylay_session *session_; spdylay_session *session_;

View File

@ -1258,10 +1258,12 @@ int Http2Session::on_write()
int Http2Session::send() int Http2Session::send()
{ {
int rv; int rv;
uint8_t buf[4096];
size_t buflen = 0;
auto output = bufferevent_get_output(bev_); auto output = bufferevent_get_output(bev_);
for(;;) { for(;;) {
// Check buffer length and return WOULDBLOCK if it is large enough. // Check buffer length and return WOULDBLOCK if it is large enough.
if(evbuffer_get_length(output) > Http2Session::OUTBUF_MAX_THRES) { if(evbuffer_get_length(output) + buflen > Http2Session::OUTBUF_MAX_THRES) {
return NGHTTP2_ERR_WOULDBLOCK; return NGHTTP2_ERR_WOULDBLOCK;
} }
@ -1276,11 +1278,30 @@ int Http2Session::send()
if(datalen == 0) { if(datalen == 0) {
break; break;
} }
if(buflen + datalen > sizeof(buf)) {
rv = evbuffer_add(output, buf, buflen);
if(rv == -1) {
SSLOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
buflen = 0;
if(datalen > static_cast<ssize_t>(sizeof(buf))) {
rv = evbuffer_add(output, data, datalen); rv = evbuffer_add(output, data, datalen);
if(rv == -1) { if(rv == -1) {
SSLOG(FATAL, this) << "evbuffer_add() failed"; SSLOG(FATAL, this) << "evbuffer_add() failed";
return -1; return -1;
} }
continue;
}
}
memcpy(buf + buflen, data, datalen);
buflen += datalen;
}
rv = evbuffer_add(output, buf, buflen);
if(rv == -1) {
SSLOG(FATAL, this) << "evbuffer_add() failed";
return -1;
} }
if(nghttp2_session_want_read(session_) == 0 && if(nghttp2_session_want_read(session_) == 0 &&

View File

@ -566,11 +566,13 @@ int Http2Upstream::on_write()
int Http2Upstream::send() int Http2Upstream::send()
{ {
int rv; int rv;
uint8_t buf[4096];
size_t buflen = 0;
auto bev = handler_->get_bev(); auto bev = handler_->get_bev();
auto output = bufferevent_get_output(bev); auto output = bufferevent_get_output(bev);
for(;;) { for(;;) {
// Check buffer length and return WOULDBLOCK if it is large enough. // Check buffer length and return WOULDBLOCK if it is large enough.
if(handler_->get_outbuf_length() > OUTBUF_MAX_THRES) { if(handler_->get_outbuf_length() + buflen > OUTBUF_MAX_THRES) {
break; break;
} }
@ -585,12 +587,32 @@ int Http2Upstream::send()
if(datalen == 0) { if(datalen == 0) {
break; break;
} }
if(buflen + datalen > sizeof(buf)) {
rv = evbuffer_add(output, buf, buflen);
if(rv == -1) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
buflen = 0;
if(datalen > static_cast<ssize_t>(sizeof(buf))) {
rv = evbuffer_add(output, data, datalen); rv = evbuffer_add(output, data, datalen);
if(rv == -1) { if(rv == -1) {
ULOG(FATAL, this) << "evbuffer_add() failed"; ULOG(FATAL, this) << "evbuffer_add() failed";
return -1; return -1;
} }
continue;
} }
}
memcpy(buf + buflen, data, datalen);
buflen += datalen;
}
rv = evbuffer_add(output, buf, buflen);
if(rv == -1) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
if(nghttp2_session_want_read(session_) == 0 && if(nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 && nghttp2_session_want_write(session_) == 0 &&
handler_->get_outbuf_length() == 0) { handler_->get_outbuf_length() == 0) {

View File

@ -58,18 +58,34 @@ ssize_t send_callback(spdylay_session *session,
auto handler = upstream->get_client_handler(); auto handler = upstream->get_client_handler();
auto bev = handler->get_bev(); auto bev = handler->get_bev();
auto output = bufferevent_get_output(bev); auto output = bufferevent_get_output(bev);
// Check buffer length and return WOULDBLOCK if it is large enough. // Check buffer length and return WOULDBLOCK if it is large enough.
if(handler->get_outbuf_length() > OUTBUF_MAX_THRES) { if(handler->get_outbuf_length() + upstream->sendbuflen > OUTBUF_MAX_THRES) {
return SPDYLAY_ERR_WOULDBLOCK; return SPDYLAY_ERR_WOULDBLOCK;
} }
if(upstream->sendbuflen + len > upstream->sendbufmax) {
rv = evbuffer_add(output, upstream->sendbuf, upstream->sendbuflen);
if(rv == -1) {
ULOG(FATAL, upstream) << "evbuffer_add() failed";
return SPDYLAY_ERR_CALLBACK_FAILURE;
}
upstream->sendbuflen = 0;
if(len > upstream->sendbufmax) {
rv = evbuffer_add(output, data, len); rv = evbuffer_add(output, data, len);
if(rv == -1) { if(rv == -1) {
ULOG(FATAL, upstream) << "evbuffer_add() failed"; ULOG(FATAL, upstream) << "evbuffer_add() failed";
return SPDYLAY_ERR_CALLBACK_FAILURE; return SPDYLAY_ERR_CALLBACK_FAILURE;
} else {
return len;
} }
} else {
memcpy(upstream->sendbuf + upstream->sendbuflen, data, len);
upstream->sendbuflen += len;
}
} else {
memcpy(upstream->sendbuf + upstream->sendbuflen, data, len);
upstream->sendbuflen += len;
}
return len;
} }
} // namespace } // namespace
@ -439,26 +455,16 @@ SpdyUpstream::~SpdyUpstream()
int SpdyUpstream::on_read() int SpdyUpstream::on_read()
{ {
int rv = 0; int rv = 0;
if((rv = spdylay_session_recv(session_)) < 0) {
rv = spdylay_session_recv(session_);
if(rv < 0) {
if(rv != SPDYLAY_ERR_EOF) { if(rv != SPDYLAY_ERR_EOF) {
ULOG(ERROR, this) << "spdylay_session_recv() returned error: " ULOG(ERROR, this) << "spdylay_session_recv() returned error: "
<< spdylay_strerror(rv); << spdylay_strerror(rv);
} }
} else if((rv = spdylay_session_send(session_)) < 0) {
ULOG(ERROR, this) << "spdylay_session_send() returned error: "
<< spdylay_strerror(rv);
}
if(rv == 0) {
if(spdylay_session_want_read(session_) == 0 &&
spdylay_session_want_write(session_) == 0 &&
handler_->get_outbuf_length() == 0) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "No more read/write for this SPDY session";
}
rv = -1;
}
}
return rv; return rv;
}
return send();
} }
int SpdyUpstream::on_write() int SpdyUpstream::on_write()
@ -470,21 +476,33 @@ int SpdyUpstream::on_write()
int SpdyUpstream::send() int SpdyUpstream::send()
{ {
int rv = 0; int rv = 0;
if((rv = spdylay_session_send(session_)) < 0) { uint8_t buf[4096];
sendbuf = buf;
sendbuflen = 0;
sendbufmax = sizeof(buf);
rv = spdylay_session_send(session_);
if(rv != 0) {
ULOG(ERROR, this) << "spdylay_session_send() returned error: " ULOG(ERROR, this) << "spdylay_session_send() returned error: "
<< spdylay_strerror(rv); << spdylay_strerror(rv);
return rv;
} }
if(rv == 0) { rv = bufferevent_write(handler_->get_bev(), sendbuf, sendbuflen);
if(rv == -1) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
if(spdylay_session_want_read(session_) == 0 && if(spdylay_session_want_read(session_) == 0 &&
spdylay_session_want_write(session_) == 0 && spdylay_session_want_write(session_) == 0 &&
handler_->get_outbuf_length() == 0) { handler_->get_outbuf_length() == 0) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "No more read/write for this SPDY session"; ULOG(INFO, this) << "No more read/write for this SPDY session";
} }
rv = -1; return -1;
} }
} return 0;
return rv;
} }
int SpdyUpstream::on_event() int SpdyUpstream::on_event()

View File

@ -67,6 +67,10 @@ public:
virtual int on_downstream_body_complete(Downstream *downstream); virtual int on_downstream_body_complete(Downstream *downstream);
bool get_flow_control() const; bool get_flow_control() const;
uint8_t *sendbuf;
size_t sendbuflen;
size_t sendbufmax;
private: private:
DownstreamQueue downstream_queue_; DownstreamQueue downstream_queue_;
ClientHandler *handler_; ClientHandler *handler_;