[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);