Fix performance degradation introduced by the second expansion feature.

I did this by adding intelligence into the algorithm such that the
second expansion was only actually performed when the prerequisite list
contained at least one "$", so we knew it is actually needed.

Without this we were using up a LOT more memory, since every single
target (even ones never used by make) had their file variables
initialized.  This also used a lot more CPU, since we needed to create
and populate a new variable hash table for every target.

There is one issue remaining with this feature: it leaks memory.  In
pattern_search() we now initialize the file variables for every pattern
target, which allocates a hash table, etc.  However, sometimes we
recursively invoke pattern_search() (for intermediate files) with an
automatic variable (alloca() I believe) as the file.  When that function
returns, obviously, the file variable hash memory is lost.
This commit is contained in:
Paul Smith 2005-04-13 03:16:33 +00:00
parent 3daf8df6ee
commit 49ee105c68
7 changed files with 117 additions and 58 deletions

View File

@ -1,3 +1,30 @@
2005-04-12 Paul D. Smith <psmith@gnu.org>
The second expansion feature causes significant slowdown. Timing
a complex makefile (GCC 4.1) shows a slowdown from .25s to just
read the makefile before the feature, to 11+s to do the same
operations after the feature. Additionally, memory usage
increased drastically. To fix this I added some intelligence that
avoids the overhead of the second expansion unless it's required.
* dep.h: Add a new boolean field, need_2nd_expansion.
* read.c (eval): When creating the struct dep for the target,
check if the name contains a "$"; if so set need_2nd_expansion to 1.
(record_files): If there's a "%" in a static pattern rule, it gets
converted to "$*" so set need_2nd_expansion to 1.
* file.c (expand_deps): Rework to be more efficient. Only perform
initialize_file_variables(), set_file_variables(), and
variable_expand_for_file() if the need_2nd_expansion is set.
* implicit.c (pattern_search): Default need_2nd_expansion to 0.
(pattern_search): Ditto.
* main.c (handle_non_switch_argument): Ditto.
(main): Ditto.
* read.c (read_all_makefiles): Ditto.
(eval_makefile): Ditto.
2005-04-07 Paul D. Smith <psmith@gnu.org> 2005-04-07 Paul D. Smith <psmith@gnu.org>
* main.c (main) [WINDOWS32]: Export PATH to sub-shells, not Path. * main.c (main) [WINDOWS32]: Export PATH to sub-shells, not Path.

1
dep.h
View File

@ -40,6 +40,7 @@ struct dep
struct file *file; struct file *file;
unsigned int changed : 8; unsigned int changed : 8;
unsigned int ignore_mtime : 1; unsigned int ignore_mtime : 1;
unsigned int need_2nd_expansion : 1;
}; };

55
file.c
View File

@ -418,28 +418,40 @@ set_intermediate (const void *item)
static void static void
expand_deps (struct file *f) expand_deps (struct file *f)
{ {
register struct dep *d, *d1; struct dep *d, *d1;
struct dep *new = 0; struct dep *new = 0;
struct dep *old = f->deps; struct dep *old = f->deps;
unsigned int last_dep_has_cmds = f->updating; unsigned int last_dep_has_cmds = f->updating;
int initialized = 0;
f->updating = 0; f->updating = 0;
f->deps = 0; f->deps = 0;
/* We are going to do second expansion so initialize file
variables for the file. */
initialize_file_variables (f, 0);
for (d = old; d != 0; d = d->next) for (d = old; d != 0; d = d->next)
{ {
if (d->name != 0) if (d->name != 0)
{ {
char *p; char *p;
struct dep **d_ptr;
set_file_variables (f); /* If we need a second expansion on these, set up the file
variables, etc. It takes a lot of extra memory and processing
to do this, so only do it if it's needed. */
if (! d->need_2nd_expansion)
p = d->name;
else
{
/* We are going to do second expansion so initialize file
variables for the file. */
if (!initialized)
{
initialize_file_variables (f, 0);
initialized = 1;
}
p = variable_expand_for_file (d->name, f); set_file_variables (f);
p = variable_expand_for_file (d->name, f);
}
/* Parse the dependencies. */ /* Parse the dependencies. */
new = (struct dep *) new = (struct dep *)
@ -452,8 +464,8 @@ expand_deps (struct file *f)
/* Files that follow '|' are special prerequisites that /* Files that follow '|' are special prerequisites that
need only exist in order to satisfy the dependency. need only exist in order to satisfy the dependency.
Their modification times are irrelevant. */ Their modification times are irrelevant. */
struct dep **d_ptr;
struct dep *d;
for (d_ptr = &new; *d_ptr; d_ptr = &(*d_ptr)->next) for (d_ptr = &new; *d_ptr; d_ptr = &(*d_ptr)->next)
; ;
++p; ++p;
@ -463,8 +475,8 @@ expand_deps (struct file *f)
parse_file_seq (&p, '\0', sizeof (struct dep), 1), parse_file_seq (&p, '\0', sizeof (struct dep), 1),
sizeof (struct dep)); sizeof (struct dep));
for (d = *d_ptr; d != 0; d = d->next) for (d1 = *d_ptr; d1 != 0; d1 = d1->next)
d->ignore_mtime = 1; d1->ignore_mtime = 1;
} }
/* Enter them as files. */ /* Enter them as files. */
@ -476,6 +488,7 @@ expand_deps (struct file *f)
else else
free (d1->name); free (d1->name);
d1->name = 0; d1->name = 0;
d1->need_2nd_expansion = 0;
} }
/* Add newly parsed deps to f->deps. If this is the last /* Add newly parsed deps to f->deps. If this is the last
@ -492,15 +505,17 @@ expand_deps (struct file *f)
{ {
if (d->next == 0 && last_dep_has_cmds) if (d->next == 0 && last_dep_has_cmds)
{ {
struct dep **d_ptr;
for (d_ptr = &new; *d_ptr; d_ptr = &(*d_ptr)->next) for (d_ptr = &new; *d_ptr; d_ptr = &(*d_ptr)->next)
; ;
*d_ptr = f->deps; *d_ptr = f->deps;
f->deps = new; f->deps = new;
} }
else else
{ {
for (d_ptr = &(f->deps); *d_ptr; d_ptr = &(*d_ptr)->next) struct dep **d_ptr;
for (d_ptr = &f->deps; *d_ptr; d_ptr = &(*d_ptr)->next)
; ;
*d_ptr = new; *d_ptr = new;
@ -509,7 +524,7 @@ expand_deps (struct file *f)
} }
} }
free_ns_chain ((struct nameseq*)old); free_ns_chain ((struct nameseq *) old);
} }
/* For each dependency of each file, make the `struct dep' point /* For each dependency of each file, make the `struct dep' point
@ -521,12 +536,12 @@ expand_deps (struct file *f)
void void
snap_deps (void) snap_deps (void)
{ {
register struct file *f; struct file *f;
register struct file *f2; struct file *f2;
register struct dep *d; struct dep *d;
register struct file **file_slot_0; struct file **file_slot_0;
register struct file **file_slot; struct file **file_slot;
register struct file **file_end; struct file **file_end;
/* Perform second expansion and enter each dependency /* Perform second expansion and enter each dependency
name as a file. */ name as a file. */

View File

@ -509,19 +509,19 @@ pattern_search (struct file *file, int archive,
break; /* No more words */ break; /* No more words */
/* If the dependency name has %, substitute the stem. /* If the dependency name has %, substitute the stem.
Watch out, we are going to do something very smart Watch out, we are going to do something tricky here. If
here. If we just replace % with the stem value, we just replace % with the stem value, later, when we do
later, when we do the second expansion, we will the second expansion, we will re-expand this stem value
re-expand this stem value once again. This is not once again. This is not good especially if you have
good especially if you have certain characters in certain characters in your setm (like $).
your setm (like $).
Instead, we will replace % with $* and allow the Instead, we will replace % with $* and allow the second
second expansion to take care of it for us. This expansion to take care of it for us. This way (since $*
way (since $* is a simple variable) there won't is a simple variable) there won't be additional
be additional re-expansion of the stem.*/ re-expansion of the stem. */
for (p2 = p; p2 < p + len && *p2 != '%'; ++p2); for (p2 = p; p2 < p + len && *p2 != '%'; ++p2)
;
if (p2 < p + len) if (p2 < p + len)
{ {
@ -836,6 +836,7 @@ pattern_search (struct file *file, int archive,
dep = (struct dep *) xmalloc (sizeof (struct dep)); dep = (struct dep *) xmalloc (sizeof (struct dep));
dep->ignore_mtime = d->ignore_mtime; dep->ignore_mtime = d->ignore_mtime;
dep->need_2nd_expansion = 0;
s = d->name; /* Hijacking the name. */ s = d->name; /* Hijacking the name. */
d->name = 0; d->name = 0;
if (recursions == 0) if (recursions == 0)
@ -908,6 +909,7 @@ pattern_search (struct file *file, int archive,
struct dep *new = (struct dep *) xmalloc (sizeof (struct dep)); struct dep *new = (struct dep *) xmalloc (sizeof (struct dep));
/* GKM FIMXE: handle '|' here too */ /* GKM FIMXE: handle '|' here too */
new->ignore_mtime = 0; new->ignore_mtime = 0;
new->need_2nd_expansion = 0;
new->name = p = (char *) xmalloc (rule->lens[i] + fullstemlen + 1); new->name = p = (char *) xmalloc (rule->lens[i] + fullstemlen + 1);
bcopy (rule->targets[i], p, bcopy (rule->targets[i], p,
rule->suffixes[i] - rule->targets[i] - 1); rule->suffixes[i] - rule->targets[i] - 1);

2
main.c
View File

@ -2125,6 +2125,7 @@ main (int argc, char **argv, char **envp)
goals->next = 0; goals->next = 0;
goals->name = 0; goals->name = 0;
goals->ignore_mtime = 0; goals->ignore_mtime = 0;
goals->need_2nd_expansion = 0;
goals->file = default_goal_file; goals->file = default_goal_file;
} }
} }
@ -2290,6 +2291,7 @@ handle_non_switch_argument (char *arg, int env)
lastgoal->name = 0; lastgoal->name = 0;
lastgoal->file = f; lastgoal->file = f;
lastgoal->ignore_mtime = 0; lastgoal->ignore_mtime = 0;
lastgoal->need_2nd_expansion = 0;
{ {
/* Add this target name to the MAKECMDGOALS variable. */ /* Add this target name to the MAKECMDGOALS variable. */

65
read.c
View File

@ -253,6 +253,7 @@ read_all_makefiles (char **makefiles)
d->file = enter_file (*p); d->file = enter_file (*p);
d->file->dontcare = 1; d->file->dontcare = 1;
d->ignore_mtime = 0; d->ignore_mtime = 0;
d->need_2nd_expansion = 0;
/* Tell update_goal_chain to bail out as soon as this file is /* Tell update_goal_chain to bail out as soon as this file is
made, and main not to die if we can't make this file. */ made, and main not to die if we can't make this file. */
d->changed = RM_DONTCARE; d->changed = RM_DONTCARE;
@ -372,6 +373,7 @@ eval_makefile (char *filename, int flags)
filename = deps->file->name; filename = deps->file->name;
deps->changed = flags; deps->changed = flags;
deps->ignore_mtime = 0; deps->ignore_mtime = 0;
deps->need_2nd_expansion = 0;
if (flags & RM_DONTCARE) if (flags & RM_DONTCARE)
deps->file->dontcare = 1; deps->file->dontcare = 1;
@ -1167,9 +1169,21 @@ eval (struct ebuffer *ebuf, int set_default)
if (beg <= end && *beg != '\0') if (beg <= end && *beg != '\0')
{ {
char *top;
const char *fromp = beg;
/* Make a copy of the dependency string. Note if we find '$'. */
deps = (struct dep*) xmalloc (sizeof (struct dep)); deps = (struct dep*) xmalloc (sizeof (struct dep));
deps->next = 0; deps->next = 0;
deps->name = savestring (beg, end - beg + 1); deps->name = top = (char *) xmalloc (end - beg + 2);
deps->need_2nd_expansion = 0;
while (fromp <= end)
{
if (*fromp == '$')
deps->need_2nd_expansion = 1;
*(top++) = *(fromp++);
}
*top = '\0';
deps->file = 0; deps->file = 0;
} }
else else
@ -1194,11 +1208,10 @@ eval (struct ebuffer *ebuf, int set_default)
commands[commands_idx++] = '\n'; commands[commands_idx++] = '\n';
} }
/* Determine if this target should be made default. We used /* Determine if this target should be made default. We used to do
to do this in record_files() but because of the delayed this in record_files() but because of the delayed target recording
target recording and because preprocessor directives are and because preprocessor directives are legal in target's commands
legal in target's commands it is too late. Consider this it is too late. Consider this fragment for example:
fragment for example:
foo: foo:
@ -1206,10 +1219,10 @@ eval (struct ebuffer *ebuf, int set_default)
... ...
endif endif
Because the target is not recorded until after ifeq Because the target is not recorded until after ifeq directive is
directive is evaluated the .DEFAULT_TARGET does not evaluated the .DEFAULT_TARGET does not contain foo yet as one
contain foo yet as one would expect. Because of this would expect. Because of this we have to move some of the logic
we have to move some of the logic here. */ here. */
if (**default_target_name == '\0' && set_default) if (**default_target_name == '\0' && set_default)
{ {
@ -1232,7 +1245,7 @@ eval (struct ebuffer *ebuf, int set_default)
#ifdef HAVE_DOS_PATHS #ifdef HAVE_DOS_PATHS
&& strchr (name, '\\') == 0 && strchr (name, '\\') == 0
#endif #endif
) )
continue; continue;
@ -1895,7 +1908,7 @@ record_files (struct nameseq *filenames, char *pattern, char *pattern_percent,
/* If there are multiple filenames, copy the chain DEPS /* If there are multiple filenames, copy the chain DEPS
for all but the last one. It is not safe for the same deps for all but the last one. It is not safe for the same deps
to go in more than one place in the data base. */ to go in more than one place in the database. */
this = nextf != 0 ? copy_dep_chain (deps) : deps; this = nextf != 0 ? copy_dep_chain (deps) : deps;
if (pattern != 0) if (pattern != 0)
@ -1912,22 +1925,20 @@ record_files (struct nameseq *filenames, char *pattern, char *pattern_percent,
this = 0; this = 0;
} }
else else
{ /* We use subst_expand to do the work of translating % to $* in
/* We use subst_expand to do the work of translating the dependency line. */
% to $* in the dependency line. */
if (this != 0 && find_percent (this->name) != 0) if (this != 0 && find_percent (this->name) != 0)
{ {
char *o; char *o;
char *buffer = variable_expand (""); char *buffer = variable_expand ("");
o = subst_expand (buffer, this->name, "%", "$*", o = subst_expand (buffer, this->name, "%", "$*", 1, 2, 0);
1, 2, 0);
free (this->name); free (this->name);
this->name = savestring (buffer, o - buffer); this->name = savestring (buffer, o - buffer);
} this->need_2nd_expansion = 1;
} }
} }
if (!two_colon) if (!two_colon)
@ -2009,7 +2020,7 @@ record_files (struct nameseq *filenames, char *pattern, char *pattern_percent,
/* This is the rule without commands. Put its /* This is the rule without commands. Put its
dependencies at the end but before dependencies dependencies at the end but before dependencies
from the rule with commands (if any). This way from the rule with commands (if any). This way
everyhting appears in makefile order. */ everything appears in makefile order. */
if (f->cmds != 0) if (f->cmds != 0)
{ {
@ -2026,7 +2037,7 @@ record_files (struct nameseq *filenames, char *pattern, char *pattern_percent,
/* This is a hack. I need a way to communicate to snap_deps() /* This is a hack. I need a way to communicate to snap_deps()
that the last dependency line in this file came with commands that the last dependency line in this file came with commands
(so that logic in snap_deps() can put it in front and all (so that logic in snap_deps() can put it in front and all
this $< -logic works). I cannot's simply rely oon file->cmds this $< -logic works). I cannot simply rely on file->cmds
being not 0 because of the cases like the following: being not 0 because of the cases like the following:
foo: bar foo: bar

1
rule.c
View File

@ -206,6 +206,7 @@ convert_suffix_rule (char *target, char *source, struct commands *cmds)
deps->next = 0; deps->next = 0;
deps->name = depname; deps->name = depname;
deps->ignore_mtime = 0; deps->ignore_mtime = 0;
deps->need_2nd_expansion = 0;
} }
create_pattern_rule (names, percents, 0, deps, cmds, 0); create_pattern_rule (names, percents, 0, deps, cmds, 0);