help-make
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: How to restrict parallelism in make? [PATCH]


From: Alexey Neyman
Subject: Re: How to restrict parallelism in make? [PATCH]
Date: Fri, 29 Apr 2005 18:45:42 +0400
User-agent: KMail/1.6.2

Hi,

an updated (an more thouroughly tested) patch to support .WAIT 
attached (includes documentation in make.texi and printing .WAIT 
dependencies).

Regards,
Alexey.

On Friday 29 April 2005 13:28, Alexey Neyman wrote:
> Hi,
> 
> I have come across the following problem with parallel jobs in make. 
I 
> have a project that uses recursive build. I'd like to have 
> subdirectories being built before the local targets. In a sequential 
> Makefile, one would do the following:
> 
> <<<<
> .PHONY: all all-subdir all-local
> all: all-subdir all-local
> all-subdir:
>       # visit the subdirectories and run "make all" there
> 
> all-local: $(whatever local targets are)
> >>>>
> 
> In parallel make, however, the 'all-local' and 'all-subdir' targets 
> will be made in parallel. Note that making 'all-local' depend on 
> 'all-subdir' won't help: the prerequisites of 'all-local' will still 
> be made in parallel with 'all-subdir'.
> 
> Well, this could be worked around by making 'all-subdir' an 
order-only 
> prerequisite to each and every target that 'all-local' depends upon, 
> and their prerequisites, and so on... Though I believe this is 
rather 
> counternatural, it helps.
> 
> And the real problems arise when I try to add the 'install' target. 
> Similarly, I'd like the 'install' target to walk into the 
> subdirectories first and then perform local activities:
> 
> <<<<
> install: install-subdir install-local
> >>>>
> 
> But if I add the install-subdir as an order-only prerequisite to all 
> targets, as described above, this will cause "make install" executed 
> in the subdirectories even in case of "make all" in the top-level 
> directory. Well, this could be worked around by having 
> 'install-subdir' target depend on 'all', but this way the 
> subdirectories will be traversed twice: once for "make all" and once 
> again for "make install". Alternatively, one could serialize the 
> targets artificially (at a cost of extra instance of make running):
> 
> <<<<
> all:
>       $(MAKE) all-subdir
>       $(MAKE) all-local
> 
> install:
>       $(MAKE) install-subdir
>       $(MAKE) install-local
> >>>>
> 
> An elegant solution to this problem has long been available in BSD 
> version of make (pmake):
> 
>  
http://www.freebsd.org/cgi/man.cgi?query=make&apropos=0&sektion=0&manpath=FreeBSD+5.3-stable&format=html
> 
> There, the special '.WAIT' target when listed as a dependency, acts 
as 
> a "serializer": all dependencies _before_ it in the list are 
finished 
> before any of those _after_ .WAIT are even started to be updated. 
> With this, the above requirements could be supported easily:
> 
> <<<<
> all: all-subdir .WAIT all-local
> install: install-subdir .WAIT install-local
> >>>>
> 
> I therefore decided to implement it in GNU make. The suggested patch 
> is attached. I also added the ability to specify the whole target as 
> requiring sequential update by specifying that target as a 
dependency 
> for the .NOTPARALLEL target (without prerequisites, it disables 
> parallelism at all).
> 
> Also attached is a test Makefile and the results of the tests. Run 
> with "make -j WAIT=.WAIT <target>", where target is one of the 
"all", 
> "all2", "all3" (they test the .WAIT behavior for implicit 
> dependencies, explicit ones and .NOTPARALLEL, respectively).
> 
> There is an alternative in the SCO version of make available 
(.MUTEX):
> 
>  http://uw713doc.sco.com/en/man/html.1/make.1.html
> 
> However, the BSD variant is more flexible, so I chose that.
> 
> Regards,
> Alexey.
> 
> -- 
> For mundane material concerns, seek the help of the one-eyed beings.
>                         -- Pkunks, SC2
> 

-- 
But wait!
This doesn't have to be the end!
                        -- Pkunks, SC2
Index: default.c
===================================================================
RCS file: /cvsroot/make/make/default.c,v
retrieving revision 1.44
diff -u -r1.44 default.c
--- default.c   16 May 2004 19:16:53 -0000      1.44
+++ default.c   29 Apr 2005 14:40:28 -0000
@@ -532,7 +532,7 @@
       char *p = default_suffixes;
       suffix_file->deps = (struct dep *)
        multi_glob (parse_file_seq (&p, '\0', sizeof (struct dep), 1),
-                   sizeof (struct dep));
+                   sizeof (struct dep), NULL);
       (void) define_variable ("SUFFIXES", 8, default_suffixes, o_default, 0);
     }
 }
Index: dep.h
===================================================================
RCS file: /cvsroot/make/make/dep.h,v
retrieving revision 1.19
diff -u -r1.19 dep.h
--- dep.h       13 Apr 2005 03:16:33 -0000      1.19
+++ dep.h       29 Apr 2005 14:40:28 -0000
@@ -41,6 +41,7 @@
     unsigned int changed : 8;
     unsigned int ignore_mtime : 1;
     unsigned int need_2nd_expansion : 1;
+    unsigned int wait : 1;
   };
 
 
@@ -53,7 +54,7 @@
   };
 
 
-extern struct nameseq *multi_glob PARAMS ((struct nameseq *chain, unsigned int 
size));
+extern struct nameseq *multi_glob PARAMS ((struct nameseq *chain, unsigned int 
size, int *seen_special));
 #ifdef VMS
 extern struct nameseq *parse_file_seq ();
 #else
Index: file.c
===================================================================
RCS file: /cvsroot/make/make/file.c,v
retrieving revision 1.78
diff -u -r1.78 file.c
--- file.c      13 Apr 2005 03:16:33 -0000      1.78
+++ file.c      29 Apr 2005 14:40:29 -0000
@@ -414,6 +414,28 @@
   f->intermediate = 1;
 }
 
+/* Notice special dependencies.  */
+static void
+notice_special_deps(struct dep **d_ptr)
+{
+  struct dep *d;
+
+restart_loop:
+  for (; *d_ptr; d_ptr = &(*d_ptr)->next)
+    {
+      d = *d_ptr;
+      if (!strcmp(d->name, ".WAIT"))
+       {
+         free(d->name);
+         if (d->next)
+           d->next->wait = 1;
+         *d_ptr = d->next;
+         free(d);
+         goto restart_loop;
+       }
+    }
+}
+
 /* Expand and parse each dependency line. */
 static void
 expand_deps (struct file *f)
@@ -422,6 +444,7 @@
   struct dep *new = 0;
   struct dep *old = f->deps;
   unsigned int last_dep_has_cmds = f->updating;
+  int seen_special = 0;
   int initialized = 0;
 
   f->updating = 0;
@@ -457,7 +480,7 @@
           new = (struct dep *)
             multi_glob (
               parse_file_seq (&p, '|', sizeof (struct dep), 1),
-              sizeof (struct dep));
+              sizeof (struct dep), &seen_special);
 
           if (*p)
             {
@@ -473,12 +496,18 @@
               *d_ptr = (struct dep *)
                 multi_glob (
                   parse_file_seq (&p, '\0', sizeof (struct dep), 1),
-                  sizeof (struct dep));
+                  sizeof (struct dep), &seen_special);
 
               for (d1 = *d_ptr; d1 != 0; d1 = d1->next)
                 d1->ignore_mtime = 1;
             }
 
+         /* Before dependencies are entered as files, treat special
+            dependencies. Need to do this before entering them as files
+            as treatment might involve deletion of a dependency.  */
+         if (seen_special)
+           notice_special_deps(&new);
+
           /* Enter them as files. */
           for (d1 = new; d1 != 0; d1 = d1->next)
             {
@@ -538,7 +567,7 @@
 {
   struct file *f;
   struct file *f2;
-  struct dep *d;
+  struct dep *d, *d2;
   struct file **file_slot_0;
   struct file **file_slot;
   struct file **file_end;
@@ -646,9 +675,20 @@
   if (f != 0 && f->is_target)
     posix_pedantic = 1;
 
+  /* .NOTPARALLEL with deps makes these targets non-parallel (i.e. all
+     their dependencies are serialized). Without deps, it forbids
+     parallel execution at all.  */
   f = lookup_file (".NOTPARALLEL");
   if (f != 0 && f->is_target)
-    not_parallel = 1;
+    {
+      if (f->deps == 0)
+       not_parallel = 1;
+      else
+       for (d = f->deps; d != 0; d = d->next)
+         for (f2 = d->file; f2 != 0; f2 = f2->prev)
+           for (d2 = f2->deps; d2 != 0; d2 = d2->next)
+             d2->wait = 1;
+    }
 
   /* Remember that we've done this. */
   snapped_deps = 1;
@@ -788,17 +828,17 @@
   /* Print all normal dependencies; note any order-only deps.  */
   for (d = f->deps; d != 0; d = d->next)
     if (! d->ignore_mtime)
-      printf (" %s", dep_name (d));
+      printf (" %s%s", d->wait ? ".WAIT " : "", dep_name (d));
     else if (! ood)
       ood = d;
 
   /* Print order-only deps, if we have any.  */
   if (ood)
     {
-      printf (" | %s", dep_name (ood));
+      printf (" | %s%s", d->wait ? ".WAIT " : "", dep_name (ood));
       for (d = ood->next; d != 0; d = d->next)
         if (d->ignore_mtime)
-          printf (" %s", dep_name (d));
+          printf (" %s%s", d->wait ? ".WAIT " : "", dep_name (d));
     }
 
   putchar ('\n');
Index: function.c
===================================================================
RCS file: /cvsroot/make/make/function.c,v
retrieving revision 1.87
diff -u -r1.87 function.c
--- function.c  4 Mar 2005 12:52:32 -0000       1.87
+++ function.c  29 Apr 2005 14:40:29 -0000
@@ -353,7 +353,7 @@
                          That would break examples like:
                          $(patsubst ./%.c,obj/%.o,$(wildcard ./?*.c)).  */
                       0),
-                     sizeof (struct nameseq));
+                     sizeof (struct nameseq), NULL);
 
   if (result == 0)
     {
Index: implicit.c
===================================================================
RCS file: /cvsroot/make/make/implicit.c,v
retrieving revision 1.47
diff -u -r1.47 implicit.c
--- implicit.c  13 Apr 2005 03:16:33 -0000      1.47
+++ implicit.c  29 Apr 2005 14:40:29 -0000
@@ -75,6 +75,7 @@
   char *intermediate_pattern;     /* pattern for intermediate file */
   unsigned char had_stem;         /* had % substituted with stem */
   unsigned char ignore_mtime;     /* ignore_mtime flag */
+  unsigned char wait;
 };
 
 static void
@@ -185,6 +186,28 @@
   return beg;
 }
 
+/* Handle special dependencies in implicit rules.  */
+static void
+notice_special_ideps(struct idep **id_ptr)
+{
+  struct idep *id;
+
+restart_loop:
+  for (; *id_ptr; id_ptr = &(*id_ptr)->next)
+    {
+      id = *id_ptr;
+      if (!strcmp(id->name, ".WAIT"))
+       {
+         free(id->name);
+         if (id->next)
+           id->next->wait = 1;
+         *id_ptr = id->next;
+         free(id);
+         goto restart_loop;
+       }
+    }
+}
+
 /* Search the pattern rules for a rule with an existing dependency to make
    FILE.  If a rule is found, the appropriate commands and deps are put in FILE
    and 1 is returned.  If not, 0 is returned.
@@ -261,6 +284,10 @@
      that is not just `%'.  */
   int specific_rule_matched = 0;
 
+  /* Nonzero if we saw possibly special dependencies (e.g. .WAIT) while
+     parsing the dependencies for that rule.  */
+  int seen_special;
+
   register unsigned int i = 0;  /* uninit checks OK */
   register struct rule *rule;
   register struct dep *dep, *expl_d;
@@ -453,6 +480,9 @@
             pattern_search won't try to use it.  */
          rule->in_use = 1;
 
+         /* This rule hasn't encountered special dependencies (so far).  */
+         seen_special = 0;
+
          /* From the lengths of the filename and the matching pattern parts,
             find the stem: the part of the filename that matches the %.  */
          stem = filename
@@ -557,14 +587,14 @@
                         multi_glob (
                           parse_file_seq (&p2,
                                           order_only ? '\0' : '|',
-                                          sizeof (struct idep),
-                                          1), sizeof (struct idep));
+                                          sizeof (struct idep), 1),
+                         sizeof (struct idep), &seen_special);
 
                       /* @@ It would be nice to teach parse_file_seq or
                          multi_glob to add prefix. This would save us
                          some reallocations. */
 
-                      if (order_only || add_dir || had_stem)
+                      if (order_only || had_stem)
                         {
                           unsigned long l = lastslash - filename + 1;
 
@@ -609,6 +639,10 @@
 
           file->stem = 0;
 
+         /* If there were special dependencies, handle them.  */
+         if (seen_special)
+           notice_special_ideps(&deps);
+
           /* @@ This loop can be combined with the previous one. I do
              it separately for now for transparency.*/
 
@@ -836,6 +870,7 @@
 
       dep = (struct dep *) xmalloc (sizeof (struct dep));
       dep->ignore_mtime = d->ignore_mtime;
+      dep->wait = d->wait;
       dep->need_2nd_expansion = 0;
       s = d->name; /* Hijacking the name. */
       d->name = 0;
@@ -908,7 +943,7 @@
        {
          struct dep *new = (struct dep *) xmalloc (sizeof (struct dep));
          /* GKM FIMXE: handle '|' here too */
-         new->ignore_mtime = 0;
+         new->ignore_mtime = new->wait = 0;
          new->need_2nd_expansion = 0;
          new->name = p = (char *) xmalloc (rule->lens[i] + fullstemlen + 1);
          bcopy (rule->targets[i], p,
Index: main.c
===================================================================
RCS file: /cvsroot/make/make/main.c,v
retrieving revision 1.202
diff -u -r1.202 main.c
--- main.c      13 Apr 2005 03:16:33 -0000      1.202
+++ main.c      29 Apr 2005 14:40:29 -0000
@@ -2108,7 +2108,7 @@
 
                     ns = multi_glob (
                       parse_file_seq (&p, '\0', sizeof (struct nameseq), 1),
-                      sizeof (struct nameseq));
+                      sizeof (struct nameseq), NULL);
 
                     /* .DEFAULT_TARGET should contain one target. */
                     if (ns->next != 0)
@@ -2124,7 +2124,7 @@
             goals = (struct dep *) xmalloc (sizeof (struct dep));
             goals->next = 0;
             goals->name = 0;
-            goals->ignore_mtime = 0;
+            goals->ignore_mtime = goals->wait = 0;
             goals->need_2nd_expansion = 0;
             goals->file = default_goal_file;
           }
@@ -2290,7 +2290,7 @@
        }
       lastgoal->name = 0;
       lastgoal->file = f;
-      lastgoal->ignore_mtime = 0;
+      lastgoal->ignore_mtime = lastgoal->wait = 0;
       lastgoal->need_2nd_expansion = 0;
 
       {
Index: read.c
===================================================================
RCS file: /cvsroot/make/make/read.c,v
retrieving revision 1.143
diff -u -r1.143 read.c
--- read.c      13 Apr 2005 03:16:33 -0000      1.143
+++ read.c      29 Apr 2005 14:40:29 -0000
@@ -252,7 +252,7 @@
              d->name = 0;
              d->file = enter_file (*p);
              d->file->dontcare = 1;
-              d->ignore_mtime = 0;
+              d->ignore_mtime = d->wait = 0;
               d->need_2nd_expansion = 0;
              /* 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.  */
@@ -366,6 +366,7 @@
   read_makefiles = deps;
   deps->name = 0;
   deps->file = lookup_file (filename);
+  deps->ignore_mtime = deps->wait = 0;
   if (deps->file == 0)
     deps->file = enter_file (xstrdup (filename));
   if (filename != ebuf.floc.filenm)
@@ -373,6 +374,7 @@
   filename = deps->file->name;
   deps->changed = flags;
   deps->ignore_mtime = 0;
+  deps->wait = 0;
   deps->need_2nd_expansion = 0;
   if (flags & RM_DONTCARE)
     deps->file->dontcare = 1;
@@ -807,7 +809,7 @@
          files = multi_glob (parse_file_seq (&p2, '\0',
                                              sizeof (struct nameseq),
                                              1),
-                             sizeof (struct nameseq));
+                             sizeof (struct nameseq), NULL);
          free (p);
 
          /* Save the state of conditionals and start
@@ -1002,7 +1004,7 @@
         filenames = multi_glob (parse_file_seq (&p2, '\0',
                                                 sizeof (struct nameseq),
                                                 1),
-                                sizeof (struct nameseq));
+                                sizeof (struct nameseq), NULL);
         *p2 = ':';
 
         if (!filenames)
@@ -2955,7 +2957,7 @@
    that have room for additional info.  */
 
 struct nameseq *
-multi_glob (struct nameseq *chain, unsigned int size)
+multi_glob (struct nameseq *chain, unsigned int size, int *seen_special)
 {
   extern void dir_setup_glob ();
   register struct nameseq *new = 0;
@@ -2983,6 +2985,10 @@
            }
        }
 
+      if (seen_special && old->name[0] == '.'
+         && isalpha (old->name[1]) && isupper (old->name[1]))
+       *seen_special = 1;
+
 #ifndef NO_ARCHIVES
       if (ar_name (old->name))
        {
Index: remake.c
===================================================================
RCS file: /cvsroot/make/make/remake.c,v
retrieving revision 1.116
diff -u -r1.116 remake.c
--- remake.c    8 Apr 2005 12:51:20 -0000       1.116
+++ remake.c    29 Apr 2005 14:40:30 -0000
@@ -361,6 +361,19 @@
     error (NILF, msg_parent, "*** ", file->name, file->parent->name, ".");
 }
 
+/* Mark the dependencies as considered in this pass (to avoid skipping them
+   on the next pass).  */
+static void
+consider_deps (struct dep *d)
+{
+  for (; d; d = d->next)
+    {
+      if (d->file->considered != considered)
+       consider_deps (d->file->deps);
+      d->file->considered = considered;
+    }
+}
+
 /* Consider a single `struct file' and update it as appropriate.  */
 
 static int
@@ -471,6 +484,13 @@
       int maybe_make;
       int dontcare = 0;
 
+      /* The code below will set the state to cs_deps_running.  */
+      if (d->wait && running)
+       {
+         consider_deps (d);
+         break;
+       }
+
       check_renamed (d->file);
 
       mtime = file_mtime (d->file);
@@ -542,20 +562,28 @@
   if (must_make || always_make_flag)
     {
       for (d = file->deps; d != 0; d = d->next)
-       if (d->file->intermediate)
-         {
-            int dontcare = 0;
+       {
+         /* The code below will set the state to cs_deps_running.  */
+         if (d->wait && running)
+           {
+             consider_deps(d);
+             break;
+           }
 
-           FILE_TIMESTAMP mtime = file_mtime (d->file);
-           check_renamed (d->file);
-           d->file->parent = file;
+         if (d->file->intermediate)
+           {
+             int dontcare = 0;
 
-            /* Inherit dontcare flag from our parent. */
-            if (rebuilding_makefiles)
-              {
-                dontcare = d->file->dontcare;
-                d->file->dontcare = file->dontcare;
-              }
+             FILE_TIMESTAMP mtime = file_mtime (d->file);
+             check_renamed (d->file);
+             d->file->parent = file;
+
+             /* Inherit dontcare flag from our parent. */
+             if (rebuilding_makefiles)
+               {
+                 dontcare = d->file->dontcare;
+                 d->file->dontcare = file->dontcare;
+               }
 
 
            dep_status |= update_file (d->file, depth);
@@ -585,7 +613,8 @@
            if (!running)
              d->changed = ((file->phony && file->cmds != 0)
                            || file_mtime (d->file) != mtime);
-         }
+           }
+       }
     }
 
   finish_updating (file);
Index: rule.c
===================================================================
RCS file: /cvsroot/make/make/rule.c,v
retrieving revision 1.37
diff -u -r1.37 rule.c
--- rule.c      13 Apr 2005 03:16:33 -0000      1.37
+++ rule.c      29 Apr 2005 14:40:30 -0000
@@ -205,7 +205,7 @@
       deps = (struct dep *) xmalloc (sizeof (struct dep));
       deps->next = 0;
       deps->name = depname;
-      deps->ignore_mtime = 0;
+      deps->ignore_mtime = deps->wait = 0;
       deps->need_2nd_expansion = 0;
     }
 
@@ -390,7 +390,7 @@
   ptr = p->dep;
   r->deps = (struct dep *) multi_glob (parse_file_seq (&ptr, '\0',
                                                        sizeof (struct dep), 1),
-                                      sizeof (struct dep));
+                                      sizeof (struct dep), NULL);
 
   if (new_pattern_rule (r, 0))
     {
Index: doc/make.texi
===================================================================
RCS file: /cvsroot/make/make/doc/make.texi,v
retrieving revision 1.27
diff -u -r1.27 make.texi
--- doc/make.texi       8 Apr 2005 12:51:20 -0000       1.27
+++ doc/make.texi       29 Apr 2005 14:40:30 -0000
@@ -2707,15 +2707,28 @@
 @xref{Variables/Recursion, ,Communicating Variables to a
 address@hidden
 
address@hidden .WAIT
address@hidden .WAIT
address@hidden parallel execution, serializing dependencies
+
+This has no special meaning when specified as a target.  When specified as
+a prerequisite, @code{.WAIT} acts as a sequence point: all dependencies
+preceding @code{.WAIT} are completed before those following @code{.WAIT}
+ are started.
+
 @findex .NOTPARALLEL
 @item .NOTPARALLEL
 @cindex parallel execution, overriding
 
-If @code{.NOTPARALLEL} is mentioned as a target, then this invocation of
address@hidden will be run serially, even if the @samp{-j} option is
-given.  Any recursively invoked @code{make} command will still be run in
-parallel (unless its makefile contains this target).  Any prerequisites
-on this target are ignored.
+If @code{.NOTPARALLEL} is mentioned as a target without prerequisites,
+then this invocation of @code{make} will be run serially, even if the
address@hidden option is given.  Any recursively invoked @code{make} command
+will still be run in parallel (unless its makefile contains this
+target).  Any prerequisites on this target are ignored.
+
+If you specify prerequisites for @code{.NOTPARALLEL}, then @code{make}
+will remake those files sequentially, as if @code{.WAIT} was specified
+before each dependency.
 @end table
 
 Any defined implicit rule suffix also counts as a special target if it

reply via email to

[Prev in Thread] Current Thread [Next in Thread]