[This is a repost of an uncommitted patch from earlier this month.
I've re-diffed against the latest code in CVS so that it will apply
cleanly.  The patch yields a reduction of about 15% in usr CPU
utilization (test case: shtml request with two subrequests).]

This patch creates a cache of pre-merged per-dir configs at
startup in order to optimize away dir-merge operations during
directory_walk and location_walk.  (Based on recent profile
data, dir-merges have ranked as one of the biggest remaining
CPU bottlenecks in 2.0.)

This cache is complementary to the per-request cache that
OtherBill recently added to dir_walk and location_walk.  The
per-request cache speeds up subrequests by using saved results
computed for the parent request, while the pre-merge cache speeds
up the the parent request itself.  In the optimal case, with no
.htaccess files, the pre-merge cache can reduce the number of
dir-merge operations during dir_walk and location_walk to zero.

I don't have pre-merge caching implemented for file_walk, but
it's probably possible to support it in the future.

--Brian


Index: include/http_config.h
===================================================================
RCS file: /home/cvspublic/httpd-2.0/include/http_config.h,v
retrieving revision 1.87
diff -u -r1.87 http_config.h
--- include/http_config.h       2001/10/02 04:09:53     1.87
+++ include/http_config.h       2001/10/28 00:54:11
@@ -846,6 +846,22 @@
                                            ap_conf_vector_t *base,
                                            ap_conf_vector_t *new_conf);
 
+/**
+ * Pre-cache the merge of new_conf onto base to speed up future 
+ap_merge_per_dir_configs calls
+ * @param base The base directory config structure
+ * @param new_conf The new directory config structure
+ * @return The merged config vector, or NULL if the merge cannot be cached
+ */
+AP_CORE_DECLARE(ap_conf_vector_t*) ap_pre_merge_per_dir_configs(apr_pool_t *p,
+                                           ap_conf_vector_t *base,
+                                           ap_conf_vector_t *new_conf);
+
+
+/**
+ * Clear the cache populated by any previous ap_pre_merge_per_dir_configs calls
+ */
+AP_CORE_DECLARE(void) ap_clear_cached_per_dir_configs();
+
 /* For http_connection.c... */
 /**
  * Setup the config vector for a connection_rec
Index: server/config.c
===================================================================
RCS file: /home/cvspublic/httpd-2.0/server/config.c,v
retrieving revision 1.136
diff -u -r1.136 config.c
--- server/config.c     2001/10/07 04:54:53     1.136
+++ server/config.c     2001/10/28 00:54:12
@@ -245,7 +245,18 @@
     return (ap_conf_vector_t *) conf_vector;
 }
 
-AP_CORE_DECLARE(ap_conf_vector_t*) ap_merge_per_dir_configs(apr_pool_t *p,
+/* Cache of precomputed values for ap_merge_per_dir_configs() */
+
+typedef struct config_cache_key {
+    ap_conf_vector_t *base;
+    ap_conf_vector_t *new_conf;
+} config_cache_key;
+
+static apr_hash_t *config_merge_cache = NULL;
+#define CONFIG_MERGE_CACHE_MAX_SIZE 1024
+
+AP_CORE_DECLARE(ap_conf_vector_t*) merge_per_dir_configs_noncached(
+                                           apr_pool_t *p,
                                            ap_conf_vector_t *base,
                                            ap_conf_vector_t *new_conf)
 {
@@ -265,6 +276,60 @@
     }
 
     return (ap_conf_vector_t *) conf_vector;
+}
+
+AP_CORE_DECLARE(ap_conf_vector_t*) ap_merge_per_dir_configs(apr_pool_t *p,
+                                           ap_conf_vector_t *base,
+                                           ap_conf_vector_t *new_conf)
+{
+    if (config_merge_cache) {
+        config_cache_key key;
+        ap_conf_vector_t *merged_conf;
+        key.base = base;
+        key.new_conf = new_conf;
+        merged_conf = apr_hash_get(config_merge_cache, &key, sizeof(key));
+        if (merged_conf) {
+            return merged_conf;
+        }
+    }
+    return merge_per_dir_configs_noncached(p, base, new_conf);
+}
+
+AP_CORE_DECLARE(ap_conf_vector_t*) ap_pre_merge_per_dir_configs(apr_pool_t *p,
+                                           ap_conf_vector_t *base,
+                                           ap_conf_vector_t *new_conf)
+{
+    config_cache_key get_key;
+    ap_conf_vector_t *merged_conf;
+    config_cache_key* set_key;
+
+    if (!config_merge_cache) {
+        config_merge_cache = apr_hash_make(p);
+    }
+    else if (apr_hash_count(config_merge_cache) >=
+             CONFIG_MERGE_CACHE_MAX_SIZE) {
+        return NULL;
+    }
+
+    get_key.base = base;
+    get_key.new_conf = new_conf;
+    merged_conf = apr_hash_get(config_merge_cache, &get_key, sizeof(get_key));
+    if (merged_conf) {
+        return merged_conf;
+    }
+
+    merged_conf = merge_per_dir_configs_noncached(p, base, new_conf);
+    set_key = apr_palloc(p, sizeof(config_cache_key));
+    set_key->base = base;
+    set_key->new_conf = new_conf;
+    apr_hash_set(config_merge_cache, set_key, sizeof(*set_key), merged_conf);
+
+    return merged_conf;
+}
+
+AP_CORE_DECLARE(void) ap_clear_cached_per_dir_configs()
+{
+    config_merge_cache = NULL;
 }
 
 static ap_conf_vector_t *create_server_config(apr_pool_t *p, server_rec *s)
Index: server/core.c
===================================================================
RCS file: /home/cvspublic/httpd-2.0/server/core.c,v
retrieving revision 1.82
diff -u -r1.82 core.c
--- server/core.c       2001/10/23 20:46:02     1.82
+++ server/core.c       2001/10/28 00:54:14
@@ -3207,6 +3207,133 @@
     ap_set_version(pconf);
 }
 
+static int is_possible_predecessor(const char *dir1, const char *dir2)
+{
+    char c1, c2;
+    while ((c1 = *dir1++)) {
+       c2 = *dir2++;
+       if (c1 != c2) {
+           if ((c1 == '*') || (c2 == '*')) {
+               while ((c1 = *dir1) && (c1 != '/'))
+                   dir1++;
+               while ((c2 = *dir2) && (c2 != '/'))
+                   dir2++;
+           }
+           else {
+               return 0;
+           }
+       }
+    }
+    return 1;
+}
+
+#define MAX_PRE_MERGE_ITEMS 8
+
+typedef struct conf_to_merge {
+    char *d;
+    int start_at;
+    ap_conf_vector_t *conf;
+} conf_to_merge;
+
+static void pre_merge_configs(apr_pool_t *p, apr_array_header_t *items,
+                              ap_conf_vector_t *defaults,
+                              apr_pool_t *plog, apr_pool_t *ptemp)
+{
+    int nelts;
+    ap_conf_vector_t **elts;
+    int i;
+    apr_array_header_t *confs_to_merge;
+
+    nelts = items->nelts;
+    if (nelts > MAX_PRE_MERGE_ITEMS) {
+        nelts = MAX_PRE_MERGE_ITEMS;
+    }
+    elts = (ap_conf_vector_t **)items->elts;
+
+    confs_to_merge = apr_array_make(ptemp, nelts, sizeof(conf_to_merge));
+
+    for (i = 0; i < nelts; i++) {
+        core_dir_config *core_elt = (core_dir_config *)
+            ap_get_module_config(elts[i], &core_module);
+        conf_to_merge *next = (conf_to_merge *)apr_array_push(confs_to_merge);
+        next->d = core_elt->d;
+        next->start_at = i + 1;
+        next->conf = elts[i];
+    }
+
+    /* Build the transitive closure of the valid merges
+     */
+    for (i = 0; i < confs_to_merge->nelts; i++) {
+        conf_to_merge *conf = (conf_to_merge *)confs_to_merge->elts + i;
+        int j;
+        int stop_now = 0;
+
+        for (j = conf->start_at; j < nelts; j++) {
+            core_dir_config *core_elt = (core_dir_config *)
+                ap_get_module_config(elts[j], &core_module);
+            if (is_possible_predecessor(conf->d, core_elt->d)) {
+                ap_conf_vector_t *merged_conf =
+                    ap_pre_merge_per_dir_configs(p, conf->conf, elts[j]);
+                if (merged_conf) {
+                    conf_to_merge *new_conf =
+                        (conf_to_merge *)apr_array_push(confs_to_merge);
+                    new_conf->d = core_elt->d;
+                    new_conf->start_at = j + 1;
+                    new_conf->conf = merged_conf;
+                }
+                else {
+                    stop_now = 1;
+                    break;
+                }
+            }
+        }
+
+        if (stop_now) {
+            break;
+        }
+    }
+
+    /* In ap_directory_walk() and ap_location_walk(), the last
+     * step is to take the pending config and merge it on top
+     * of the default config for the requested server.  By
+     * pre-caching the merge here, we can avoid it at request time.
+     */
+    if (defaults) {
+        for (i = 0; i < confs_to_merge->nelts; i++) {
+            conf_to_merge *conf = (conf_to_merge *)confs_to_merge->elts + i;
+            if (!ap_pre_merge_per_dir_configs(p, defaults, conf->conf)) {
+                break;
+            }
+        }
+    }
+}
+
+static void core_post_config_merge(apr_pool_t *p, apr_pool_t *plog,
+                                  apr_pool_t *ptemp, server_rec *s)
+{
+    server_rec *virt;
+
+    /* Because the configuration processing happens twice during
+     * startup, the first pass ends up caching pointers that are
+     * invalid by the second pass (because they point into pools
+     * that no longer exist).  So we need to clear the old contents
+     * of the cache:
+     */
+    ap_clear_cached_per_dir_configs();
+
+    /* Pre-merge the configs for <Directory> and <Location> blocks
+     * to speed up request processing
+     */
+    for (virt = s; virt; virt = virt->next) {
+        core_server_config *sconf;
+        sconf = ap_get_module_config(virt->module_config, &core_module);
+       pre_merge_configs(p, sconf->sec_dir, virt->lookup_defaults,
+                          plog, ptemp);
+       pre_merge_configs(p, sconf->sec_url, virt->lookup_defaults,
+                          plog, ptemp);
+    }
+}
+
 static void core_open_logs(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, 
server_rec *s)
 {
     ap_open_logs(s, pconf);
@@ -3257,6 +3384,7 @@
 static void register_hooks(apr_pool_t *p)
 {
     ap_hook_post_config(core_post_config,NULL,NULL,APR_HOOK_REALLY_FIRST);
+    ap_hook_post_config(core_post_config_merge,NULL,NULL,APR_HOOK_REALLY_LAST);
     ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);
     ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);
     ap_hook_open_logs(core_open_logs,NULL,NULL,APR_HOOK_MIDDLE);

Reply via email to