help-make
[Top][All Lists]
Advanced

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

How to restrict parallelism in make? [PATCH]


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

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
all: a b $(WAIT) c d s-e s-f
all2: a b c $(WAIT) d e $(WAIT) f g h $(WAIT) i $(WAIT) $(WAIT) j k
all3: a b sub3 i j

.PHONY: all1 all2 all3 sub3 clean

sub3: c d e f g h
.NOTPARALLEL: sub3

clean:
        rm -f [a-k] [stu]-*

a b c d e f g h i j k:
        @echo Starting update of address@hidden; sleep 1; touch $@; echo 
Finished update of address@hidden

s-%: u-% $(WAIT) t-%
        @echo Starting update of address@hidden; sleep 1; touch $@; echo 
Finished update of address@hidden

t-%:
        @echo Starting update of address@hidden; sleep 1; touch $@; echo 
Finished update of address@hidden

u-%:
        @echo Starting update of address@hidden; sleep 1; touch $@; echo 
Finished update of address@hidden
$ ../make/make clean
rm -f [a-k] [stu]-*
$ ../make/make -j all WAIT=.WAIT
Starting update of a...
Starting update of b...
Finished update of a.
Finished update of b.
Starting update of c...
Starting update of d...
Starting update of u-f...
Starting update of u-e...
Finished update of d.
Finished update of u-f.
Finished update of c.
Finished update of u-e.
Starting update of t-f...
Starting update of t-e...
Finished update of t-f.
Finished update of t-e.
Starting update of s-f...
Starting update of s-e...
Finished update of s-f.
Finished update of s-e.
rm t-f u-f t-e u-e
$ ../make/make clean
rm -f [a-k] [stu]-*
$ ../make/make -j all2 WAIT=.WAIT
Starting update of a...
Starting update of b...
Starting update of c...
Finished update of a.
Finished update of b.
Finished update of c.
Starting update of d...
Starting update of e...
Finished update of d.
Finished update of e.
Starting update of f...
Starting update of g...
Starting update of h...
Finished update of f.
Finished update of g.
Finished update of h.
Starting update of i...
Finished update of i.
Starting update of j...
Starting update of k...
Finished update of k.
Finished update of j.
$ ../make/make clean
rm -f [a-k] [stu]-*
$ ../make/make -j all3 WAIT=.WAIT
Starting update of a...
Starting update of b...
Starting update of c...
Starting update of i...
Starting update of j...
Finished update of a.
Finished update of b.
Finished update of c.
Finished update of i.
Starting update of d...
Finished update of j.
Finished update of d.
Starting update of e...
Finished update of e.
Starting update of f...
Finished update of f.
Starting update of g...
Finished update of g.
Starting update of h...
Finished update of h.
Index: default.c
===================================================================
RCS file: /cvsroot/make/make/default.c,v
retrieving revision 1.44
diff -u -u -r1.44 default.c
--- default.c   16 May 2004 19:16:53 -0000      1.44
+++ default.c   29 Apr 2005 08:38:51 -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 -u -r1.19 dep.h
--- dep.h       13 Apr 2005 03:16:33 -0000      1.19
+++ dep.h       29 Apr 2005 08:38:51 -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 -u -r1.78 file.c
--- file.c      13 Apr 2005 03:16:33 -0000      1.78
+++ file.c      29 Apr 2005 08:39:23 -0000
@@ -414,6 +414,28 @@
   f->intermediate = 1;
 }
 
+/* Notice special dependencies.  */
+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;
Index: filedef.h
===================================================================
RCS file: /cvsroot/make/make/filedef.h,v
retrieving revision 2.22
diff -u -u -r2.22 filedef.h
--- filedef.h   28 Feb 2005 07:48:22 -0000      2.22
+++ filedef.h   29 Apr 2005 08:39:23 -0000
@@ -113,6 +113,7 @@
 extern void notice_finished_file PARAMS ((struct file *file));
 extern void init_hash_files PARAMS ((void));
 extern char *build_target_list PARAMS ((char *old_list));
+extern void notice_special_deps PARAMS ((struct dep **d));
 
 #if FILE_TIMESTAMP_HI_RES
 # define FILE_TIMESTAMP_STAT_MODTIME(fname, st) \
Index: function.c
===================================================================
RCS file: /cvsroot/make/make/function.c,v
retrieving revision 1.87
diff -u -u -r1.87 function.c
--- function.c  4 Mar 2005 12:52:32 -0000       1.87
+++ function.c  29 Apr 2005 08:39:24 -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 -u -r1.47 implicit.c
--- implicit.c  13 Apr 2005 03:16:33 -0000      1.47
+++ implicit.c  29 Apr 2005 08:39:24 -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 -u -r1.202 main.c
--- main.c      13 Apr 2005 03:16:33 -0000      1.202
+++ main.c      29 Apr 2005 08:39:24 -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 -u -r1.143 read.c
--- read.c      13 Apr 2005 03:16:33 -0000      1.143
+++ read.c      29 Apr 2005 08:39:24 -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 -u -r1.116 remake.c
--- remake.c    8 Apr 2005 12:51:20 -0000       1.116
+++ remake.c    29 Apr 2005 08:39:24 -0000
@@ -361,6 +361,15 @@
     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)
+    d->file->considered = considered;
+}
+
 /* Consider a single `struct file' and update it as appropriate.  */
 
 static int
@@ -471,6 +480,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 +558,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 +609,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 -u -r1.37 rule.c
--- rule.c      13 Apr 2005 03:16:33 -0000      1.37
+++ rule.c      29 Apr 2005 08:39:24 -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;
     }
 
@@ -365,6 +365,7 @@
 {
   register struct rule *r;
   char *ptr;
+  int seen_special = 0;
 
   r = (struct rule *) xmalloc (sizeof (struct rule));
 
@@ -390,7 +391,10 @@
   ptr = p->dep;
   r->deps = (struct dep *) multi_glob (parse_file_seq (&ptr, '\0',
                                                        sizeof (struct dep), 1),
-                                      sizeof (struct dep));
+                                      sizeof (struct dep), &seen_special);
+
+  if (seen_special)
+       notice_special_deps(&r->deps);
 
   if (new_pattern_rule (r, 0))
     {

reply via email to

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