mirror of
https://github.com/mirror/make.git
synced 2025-02-26 11:50:42 +08:00
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:
parent
3daf8df6ee
commit
49ee105c68
27
ChangeLog
27
ChangeLog
@ -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
1
dep.h
@ -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
55
file.c
@ -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. */
|
||||||
|
24
implicit.c
24
implicit.c
@ -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
2
main.c
@ -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
65
read.c
@ -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
1
rule.c
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user