You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Mike Abbott <mj...@trudge.engr.sgi.com> on 1999/07/15 02:24:44 UTC

[PATCH] 10x performance increase patch #2

Subject: [PATCH] 10x performance increase patch #2

Apache Developers,

Here is the second patch in my series that increase Apache's performance
ten-fold.  This one streamlines request parsing.

The vanilla Apache parsing functions read_request_line() and
get_mime_headers() make multiple passes and many copies.  I wrote new
functions to parse common HTTP request lines and headers in a single
pass, copying minimal data.  The streamlined parsing routines
quickly_read_request_line() and quickly_get_mime_headers() do not handle
all the edge cases, nor do they have to:  When they detect a construct
they cannot parse quickly they give up and fall back on the vanilla
Apache parsing functions.  They are meant to parse the simple, common
cases quickly and to recognize when they cannot do so.

I also included a couple minor tweaks that speed up the vanilla parsing
path.

This patch may break binary compatibility of modules because it adds
fields to the request_rec structure.

The base for this patch is Apache/1.3.6 + 10x performance increase patch
#1.

Again, I welcome your review, questions, and feedback.
--
Michael J. Abbott    mja@sgi.com

========================================================================

diff -Naur apache_1.3.6+01/src/include/ap_config.h apache_1.3.6+01-02/src/include/ap_config.h
--- apache_1.3.6+01/src/include/ap_config.h	Thu Jul  8 12:11:36 1999
+++ apache_1.3.6+01-02/src/include/ap_config.h	Wed Jul 14 15:46:15 1999
@@ -210,6 +210,7 @@
 #define NO_LONG_DOUBLE
 #define NO_LINGCLOSE
 #define HAVE_SYSLOG 1
+#define CACHE_ALIGNMENT 128	/* best value for most SGI servers */
 
 #elif defined(HIUX)
 #undef HAVE_GMTOFF
@@ -1196,6 +1197,22 @@
  */
 #if defined(__WCOREDUMP) && !defined(WCOREDUMP)
 #define WCOREDUMP __WCOREDUMP
+#endif
+
+/*
+ * Aligning the address and length of certain key data structures and
+ * buffers to exactly match cache lines can dramatically increase
+ * performance.  In the absence of an architecture-specific
+ * CACHE_ALIGNMENT definition from above, assume this arbitrary default.
+ * Set to 0 to disable alignment.  When adding your own specific version
+ * above, use the larger of your processor's primary and secondary cache
+ * line sizes.
+ */
+#ifndef CACHE_ALIGNMENT
+#define CACHE_ALIGNMENT 32	/* in bytes */
+#endif
+#if CACHE_ALIGNMENT & (CACHE_ALIGNMENT - 1)
+#error "CACHE_ALIGNMENT must be a power of two"
 #endif
 
 #ifdef SUNOS_LIB_PROTOTYPES
diff -Naur apache_1.3.6+01/src/include/httpd.h apache_1.3.6+01-02/src/include/httpd.h
--- apache_1.3.6+01/src/include/httpd.h	Thu Jul  8 12:14:00 1999
+++ apache_1.3.6+01-02/src/include/httpd.h	Wed Jul 14 15:47:23 1999
@@ -779,6 +779,10 @@
  * record to improve 64bit alignment the next time we need to break
  * binary compatibility for some other reason.
  */
+
+    char *rqbuf;		/* quickly_read_request_line()'s buffer */
+    char *rqhdr;		/* quickly_get_mime_headers()'s headers */
+    int rqhdr_len;		/* strlen(rqhdr) */
 };
 
 
diff -Naur apache_1.3.6+01/src/main/http_protocol.c apache_1.3.6+01-02/src/main/http_protocol.c
--- apache_1.3.6+01/src/main/http_protocol.c	Thu Jul  8 12:28:05 1999
+++ apache_1.3.6+01-02/src/main/http_protocol.c	Wed Jul 14 17:19:12 1999
@@ -771,11 +771,117 @@
     }
 }
 
+#ifndef RQBUF_SIZE 
+#define RQBUF_SIZE  4096    /* should be >= buff.c's DEFAULT_BUFSIZE */
+#endif
+
+/*
+ * Read the request and, as quickly as possible with minimal copying,
+ * parse it or pass it on to the regular parsing function
+ * read_request_line().  Shares state with quickly_get_mime_headers(),
+ * which has a similar mission.
+ */
+static int
+quickly_read_request_line(request_rec *r)
+{
+    BUFF *fb;
+    int n;
+
+    /*
+     * If r->rqbuf already exists, there could be pointers into it from
+     * r->headers_in so instead of reusing it, just replace it.
+     */
+#if CACHE_ALIGNMENT > 0
+    /* align r->rqbuf on a cache line boundary */
+    r->rqbuf = ap_palloc(r->pool, RQBUF_SIZE + CACHE_ALIGNMENT - 1);
+    r->rqbuf = (char *) (((ap_ptr) r->rqbuf + CACHE_ALIGNMENT - 1) & ~(CACHE_ALIGNMENT - 1));
+#else
+    r->rqbuf = ap_palloc(r->pool, RQBUF_SIZE);
+#endif
+
+    /*
+     * would read directly into fb->inptr but r->the_request needs an
+     * intact copy, sigh
+     */
+    fb = r->connection->client;
+    fb->flags |= B_SAFEREAD;
+    n = ap_bread(fb, r->rqbuf, RQBUF_SIZE);
+    fb->flags &= ~B_SAFEREAD;
+
+    /* 16 == strlen("GET / HTTP/1.0\n\n"):  minimal valid request */
+    if (n >= 16 &&
+	r->rqbuf[0] == 'G' &&
+	r->rqbuf[1] == 'E' &&
+	r->rqbuf[2] == 'T' &&
+	r->rqbuf[3] == ' ') {
+	char *cp, *ep;
+
+	/* find the end of the uri */
+	cp = &r->rqbuf[4];		/* 4 == strlen("GET ") */
+	ep = &r->rqbuf[n - 11];		/* 11 == strlen(" HTTP/1.0\n\n") */
+	while (cp <= ep && !ap_isspace(*cp))
+	    cp++;
+
+	if (cp <= ep &&
+	    cp[0] == ' ' &&
+	    cp[1] == 'H' &&
+	    cp[2] == 'T' &&
+	    cp[3] == 'T' &&
+	    cp[4] == 'P' &&
+	    cp[5] == '/' &&
+	    cp[6] == '1' &&
+	    cp[7] == '.' &&
+	    (cp[8] == '0' || cp[8] == '1') &&
+	    ((cp[9] == '\r' && cp[10] == '\n') || cp[9] == '\n')) {
+
+	    r->rqhdr = cp + 10 + (cp[9] == '\r');
+	    r->rqhdr_len = (int) (&r->rqbuf[n] - r->rqhdr);
+	    cp[9] = 0;	    /* null-terminate r->the_request */
+
+	    r->request_time = time(NULL);
+	    r->the_request = r->rqbuf;
+	    r->method = "GET";
+	    r->method_number = M_GET;
+	    ap_assert(!r->assbackwards);
+
+	    if (cp[8] == '0') {
+		r->protocol = "HTTP/1.0";
+		r->proto_num = HTTP_VERSION(1,0);
+	    } else {
+		r->protocol = "HTTP/1.1";
+		r->proto_num = HTTP_VERSION(1,1);
+	    }
+
+	    *cp = 0;	/* avoid copying uri; briefly mangles r->the_request */
+	    ap_parse_uri(r, &r->rqbuf[4]);
+	    *cp = ' ';
+
+	    return 1;
+	}
+    }
+
+    /*
+     * Shortcut parsing failed.  No harm done, just copy the
+     * already-read data into fb and fall back to regular processing.
+     * If copying bothers you, extend the buffering code to allow an
+     * alternate read-ahead buffer and then just manipulate pointers.
+     */
+    ap_assert(r->rqhdr_len == 0);
+    if (n > 0) {
+	ap_assert(&fb->inptr[n] <= &fb->inbase[fb->bufsiz]);
+	memcpy(fb->inptr, r->rqbuf, n);
+	fb->incnt += n;
+    }
+
+    return 0;
+}
+
 static int read_request_line(request_rec *r)
 {
     char l[DEFAULT_LIMIT_REQUEST_LINE + 2]; /* getline's two extra for \n\0 */
     const char *ll = l;
     const char *uri;
+    const char *pro;
     conn_rec *conn = r->connection;
     int major = 1, minor = 0;   /* Assume HTTP/1.0 if non-"HTTP" protocol */
     int len;
@@ -809,7 +915,7 @@
     ap_bsetflag(conn->client, B_SAFEREAD, 0);
 
     r->request_time = time(NULL);
-    r->the_request = ap_pstrdup(r->pool, l);
+    r->the_request = ap_pstrndup(r->pool, l, len);
     r->method = ap_getword_white(r->pool, &ll);
     uri = ap_getword_white(r->pool, &ll);
 
@@ -834,9 +940,23 @@
     }
 
     r->assbackwards = (ll[0] == '\0');
-    r->protocol = ap_pstrdup(r->pool, ll[0] ? ll : "HTTP/0.9");
+
+    if (ll[0]) {
+	pro = ll;
+	len = ap_strlen(ll);
+    } else {
+	pro = "HTTP/0.9";
+	len = 8;
+    }
+    r->protocol = ap_pstrndup(r->pool, pro, len);
 
-    if (2 == sscanf(r->protocol, "HTTP/%u.%u", &major, &minor)
+    /* avoid sscanf in the common case */
+    if (len == 8 &&
+	pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P' &&
+	pro[4] == '/' && ap_isdigit(pro[5]) && pro[6] == '.' &&
+	ap_isdigit(pro[7])) {
+	r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0');
+    } else if (2 == sscanf(r->protocol, "HTTP/%u.%u", &major, &minor)
       && minor < HTTP_VERSION(1,0))	/* don't allow HTTP/0.1000 */
 	r->proto_num = HTTP_VERSION(major, minor);
     else
@@ -845,6 +965,133 @@
     return 1;
 }
 
+/*
+ * Parse the request options as quickly as possible.  Requires state
+ * from quickly_read_request_line().  Handles only the fast cases, and
+ * detects and defers the slow ones to the regular parser
+ * get_mime_headers().
+ */
+static int
+quickly_get_mime_headers(request_rec *r)
+{
+    int ok, nh, mh;
+    table *headers;
+    char *hp, *ep;
+    const char *h1name, *h1value;
+    int remain;
+
+    ok = 0;
+
+    nh = ap_table_elts(r->headers_in)->nelts;
+    mh = r->server->limit_req_fields;
+    if (mh <= 0)
+	mh = 65536;	/* essentially infinite */
+    headers = NULL;
+
+    hp = r->rqhdr;
+    ep = hp + r->rqhdr_len;
+    r->rqhdr_len = 0;	/* never re-parse */
+
+    h1name = NULL;
+#ifdef __GNUC__
+    h1value = NULL;	/* appease stupid compiler */
+#endif
+
+    while (hp < ep && nh < mh) {
+	char *name, *value;
+
+	name = hp;
+
+	/* find a : (end of name) */
+	value = name;
+	while (value < ep && *value != ':' && !ap_isspace(*value))
+	    value++;
+
+	/* complete name? */
+	if (value > name && value < ep && *value == ':') {
+	    *value = 0;		/* null-terminate name */
+
+	    /* skip white space */
+	    do
+		value++;
+	    while (value < ep && (*value == ' ' || *value == '\t'));
+
+	    /* find end of value */
+	    hp = value;
+	    while (hp < ep && *hp != '\n')
+		hp++;
+
+	    /*
+	     * complete value?  beware leading-space continuation lines,
+	     * or the threat of one if we're right at the end of rqbuf.
+	     * (there must be at least one more byte left and it must
+	     * not be a leading space)
+	     */
+	    if (hp + 1 < ep && hp[1] != ' ' && hp[1] != '\t') {
+		hp[-(hp[-1] == '\r')] = 0;	/* null-terminate value */
+		hp++;
+
+		if (headers) {
+		    /* insert into established table */
+		    ap_table_addn(headers, name, value);
+		} else if (h1name) {
+		    /*
+		     * More than one header (this is the second).  Make
+		     * a table for them.
+		     */
+		    headers = ap_make_table(r->pool, 16);
+		    ap_table_addn(headers, h1name, h1value);
+		    ap_table_addn(headers, name, value);
+		} else {
+		    /* first header, keep it close in case there are no more */
+		    h1name = name;
+		    h1value = value;
+		}
+
+		nh++;
+	    } else {
+		hp = name;
+		do
+		    name++;
+		while (*name);
+		*name = ':';	/* restore : for copy below */
+		break;
+	    }
+	} else {
+	    if (hp < ep && *hp == '\r')
+		hp++;
+	    if (hp < ep && *hp == '\n') {
+		hp++;
+		ok = 1;		/* end of header section */
+	    }
+	    break;
+	}
+    }
+
+    /*
+     * ap_overlap_tables() is expensive so avoid it when possible.
+     * Right now we skip it only when there are fewer than two headers,
+     * but we could also skip it if we determine that there are no
+     * merges required, i.e., there are no duplicate headers.  Then we
+     * could just append headers onto r->headers_in.
+     */
+    if (headers)
+	ap_overlap_tables(r->headers_in, headers, AP_OVERLAP_TABLES_MERGE);
+    else if (h1name)
+	ap_table_mergen(r->headers_in, h1name, h1value);
+
+    remain = (int) (ep - hp);
+    if (remain > 0) {
+	BUFF *fb = r->connection->client;
+
+	ap_assert(&fb->inptr[remain] <= &fb->inbase[fb->bufsiz]);
+	memcpy(fb->inptr, hp, remain);
+	fb->incnt += remain;
+    }
+
+    return ok;
+}
+
 static void get_mime_headers(request_rec *r)
 {
     char field[DEFAULT_LIMIT_REQUEST_FIELDSIZE + 2]; /* getline's two extra */
@@ -852,7 +1099,7 @@
     char *value;
     char *copy;
     int len;
-    unsigned int fields_read = 0;
+    unsigned int fields_read = ap_table_elts(r->headers_in)->nelts;
     table *tmp_headers;
 
     /* We'll use ap_overlap_tables later to merge these into r->headers_in. */
@@ -934,13 +1181,9 @@
     r->request_config  = ap_create_request_config(r->pool);
     r->per_dir_config  = r->server->lookup_defaults;
 
-    r->sent_bodyct     = 0;                      /* bytect isn't for body */
-
-    r->read_length     = 0;
     r->read_body       = REQUEST_NO_BODY;
 
     r->status          = HTTP_REQUEST_TIME_OUT;  /* Until we get a request */
-    r->the_request     = NULL;
 
 #ifdef CHARSET_EBCDIC
     ap_bsetflag(r->connection->client, B_ASCII2EBCDIC|B_EBCDIC2ASCII, 1);
@@ -949,7 +1192,7 @@
     /* Get the request... */
 
     ap_keepalive_timeout("read request line", r);
-    if (!read_request_line(r)) {
+    if (!quickly_read_request_line(r) && !read_request_line(r)) {
         ap_kill_timeout(r);
         if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
 
@@ -964,7 +1207,8 @@
     }
     if (!r->assbackwards) {
         ap_hard_timeout("read request headers", r);
-        get_mime_headers(r);
+        if (!quickly_get_mime_headers(r))
+	    get_mime_headers(r);
         ap_kill_timeout(r);
         if (r->status != HTTP_REQUEST_TIME_OUT) {
             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,

Re: [PATCH] 10x performance increase patch #2

Posted by Dean Gaudet <dg...@arctic.org>.
On Wed, 14 Jul 1999, Mike Abbott wrote:

> The vanilla Apache parsing functions read_request_line() and
> get_mime_headers() make multiple passes and many copies.  I wrote new
> functions to parse common HTTP request lines and headers in a single
> pass, copying minimal data.  The streamlined parsing routines
> quickly_read_request_line() and quickly_get_mime_headers() do not handle
> all the edge cases, nor do they have to:  When they detect a construct
> they cannot parse quickly they give up and fall back on the vanilla
> Apache parsing functions.  They are meant to parse the simple, common
> cases quickly and to recognize when they cannot do so.

nice... this is the technique I used in my flow prototype a long time ago,
nice to see it for apache.

Dean