diff mbox

[U-Boot] buildman: allow more incremental building

Message ID 1459204775-23510-1-git-send-email-swarren@wwwdotorg.org
State Changes Requested
Delegated to: Simon Glass
Headers show

Commit Message

Stephen Warren March 28, 2016, 10:39 p.m. UTC
From: Stephen Warren <swarren@nvidia.com>

One use-case for buildman is to continually run it interactively after
each small step in a large refactoring operation. This gives more
immediate feedback than making a number of commits and then going back and
testing them. For this to work well, buildman needs to be extremely fast.
At present, a couple issues prevent it being as fast as it could be:

1) Each time buildman runs "make %_defconfig", it runs "make mrproper"
first. This throws away all previous build results, requiring a
from-scratch build. Optionally avoiding this would speed up the build, at
the cost of potentially causing or missing some build issues.

2) A build tree is created per thread rather than per board. When a thread
switches between building different boards, this often causes many files
to be rebuilt due to changing config options. Using a separate build tree
for each board would avoid this. This does put more strain on the system's
disk cache, but it is worth it on my system at least.

This commit adds two command-line options to implement the changes
described above; -I ("--incremental") turns of "make mrproper" and -P
("--per-board-out-dir") creats a build directory per board rather than per
thread.

Tested:

    ./tools/buildman/buildman.py tegra
    ./tools/buildman/buildman.py -I -P tegra
    ./tools/buildman/buildman.py -b tegra_dev tegra
    ./tools/buildman/buildman.py -b tegra_dev -I -P tegra

... each once after deleting the buildman result/work directory, and once
"incrementally" after a previous identical invocation.

Signed-off-by: Stephen Warren <swarren@nvidia.com>
---
 tools/buildman/builder.py       | 10 ++++++++--
 tools/buildman/builderthread.py | 24 ++++++++++++++++--------
 tools/buildman/cmdline.py       |  4 ++++
 tools/buildman/control.py       |  4 +++-
 4 files changed, 31 insertions(+), 11 deletions(-)

Comments

Tom Rini April 1, 2016, 1:35 p.m. UTC | #1
On Mon, Mar 28, 2016 at 04:39:35PM -0600, Stephen Warren wrote:

> From: Stephen Warren <swarren@nvidia.com>
> 
> One use-case for buildman is to continually run it interactively after
> each small step in a large refactoring operation. This gives more
> immediate feedback than making a number of commits and then going back and
> testing them. For this to work well, buildman needs to be extremely fast.
> At present, a couple issues prevent it being as fast as it could be:
> 
> 1) Each time buildman runs "make %_defconfig", it runs "make mrproper"
> first. This throws away all previous build results, requiring a
> from-scratch build. Optionally avoiding this would speed up the build, at
> the cost of potentially causing or missing some build issues.
> 
> 2) A build tree is created per thread rather than per board. When a thread
> switches between building different boards, this often causes many files
> to be rebuilt due to changing config options. Using a separate build tree
> for each board would avoid this. This does put more strain on the system's
> disk cache, but it is worth it on my system at least.
> 
> This commit adds two command-line options to implement the changes
> described above; -I ("--incremental") turns of "make mrproper" and -P
> ("--per-board-out-dir") creats a build directory per board rather than per
> thread.
> 
> Tested:
> 
>     ./tools/buildman/buildman.py tegra
>     ./tools/buildman/buildman.py -I -P tegra
>     ./tools/buildman/buildman.py -b tegra_dev tegra
>     ./tools/buildman/buildman.py -b tegra_dev -I -P tegra
> 
> ... each once after deleting the buildman result/work directory, and once
> "incrementally" after a previous identical invocation.
> 
> Signed-off-by: Stephen Warren <swarren@nvidia.com>

Reviewed-by: Tom Rini <trini@konsulko.com>
Simon Glass April 11, 2016, 12:04 p.m. UTC | #2
Hi,

On 1 April 2016 at 07:35, Tom Rini <trini@konsulko.com> wrote:
> On Mon, Mar 28, 2016 at 04:39:35PM -0600, Stephen Warren wrote:
>
>> From: Stephen Warren <swarren@nvidia.com>
>>
>> One use-case for buildman is to continually run it interactively after
>> each small step in a large refactoring operation. This gives more
>> immediate feedback than making a number of commits and then going back and
>> testing them. For this to work well, buildman needs to be extremely fast.
>> At present, a couple issues prevent it being as fast as it could be:
>>
>> 1) Each time buildman runs "make %_defconfig", it runs "make mrproper"
>> first. This throws away all previous build results, requiring a
>> from-scratch build. Optionally avoiding this would speed up the build, at
>> the cost of potentially causing or missing some build issues.

Does it actually cause problems? I think I have seen things go wrong
before, which is why I added it, but I can't remember the detail.

Threads work by configuring and building the first commit for a board,
then incrementally building each commit for that same board. So it
seems like change should only be useful when all the boards you are
building have a similar config.

>>
>> 2) A build tree is created per thread rather than per board. When a thread
>> switches between building different boards, this often causes many files
>> to be rebuilt due to changing config options. Using a separate build tree
>> for each board would avoid this. This does put more strain on the system's
>> disk cache, but it is worth it on my system at least.

I'm not sure why this is a win, given what I said above. I'm starting
to feel that I don't understand what is going on.

>>
>> This commit adds two command-line options to implement the changes
>> described above; -I ("--incremental") turns of "make mrproper" and -P
>> ("--per-board-out-dir") creats a build directory per board rather than per
>> thread.
>>
>> Tested:
>>
>>     ./tools/buildman/buildman.py tegra
>>     ./tools/buildman/buildman.py -I -P tegra
>>     ./tools/buildman/buildman.py -b tegra_dev tegra
>>     ./tools/buildman/buildman.py -b tegra_dev -I -P tegra
>>
>> ... each once after deleting the buildman result/work directory, and once
>> "incrementally" after a previous identical invocation.
>>
>> Signed-off-by: Stephen Warren <swarren@nvidia.com>
>
> Reviewed-by: Tom Rini <trini@konsulko.com>

Oddly I don't seem to have received the original email, but I see this
in patchwork.

For me this cut build time from 6.25 minutes to 4.75 minutes for 30
commits built for two similar boards on a 8-thread machine. It's a big
win. I haven't yet tried it out on a a big build.

Stephen can you please update the README with some of your commit
notes? Other than that:

Acked-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org>

Regards,
Simon
Stephen Warren April 11, 2016, 4:15 p.m. UTC | #3
On 04/11/2016 06:04 AM, Simon Glass wrote:
> Hi,
>
> On 1 April 2016 at 07:35, Tom Rini <trini@konsulko.com> wrote:
>> On Mon, Mar 28, 2016 at 04:39:35PM -0600, Stephen Warren wrote:
>>
>>> From: Stephen Warren <swarren@nvidia.com>
>>>
>>> One use-case for buildman is to continually run it interactively after
>>> each small step in a large refactoring operation. This gives more
>>> immediate feedback than making a number of commits and then going back and
>>> testing them. For this to work well, buildman needs to be extremely fast.
>>> At present, a couple issues prevent it being as fast as it could be:
>>>
>>> 1) Each time buildman runs "make %_defconfig", it runs "make mrproper"
>>> first. This throws away all previous build results, requiring a
>>> from-scratch build. Optionally avoiding this would speed up the build, at
>>> the cost of potentially causing or missing some build issues.
>
> Does it actually cause problems? I think I have seen things go wrong
> before, which is why I added it, but I can't remember the detail.

I have not noticed any issues using -I and hence skipping the mrproper. 
It is possible you added the mrproper before the conversion to Kbuild; 
Kbuild seems to handle rebuilding after code changes much better than 
the old system, which definitely had some flaws that prevented 
incremental builds in many cases.

> Threads work by configuring and building the first commit for a board,
> then incrementally building each commit for that same board. So it
> seems like change should only be useful when all the boards you are
> building have a similar config.

No, because of point 2 below.

>>> 2) A build tree is created per thread rather than per board. When a thread
>>> switches between building different boards, this often causes many files
>>> to be rebuilt due to changing config options. Using a separate build tree
>>> for each board would avoid this. This does put more strain on the system's
>>> disk cache, but it is worth it on my system at least.
>
> I'm not sure why this is a win, given what I said above. I'm starting
> to feel that I don't understand what is going on.

Since each board gets built in a separate directory, there's no build 
churn due to the configuration being changed when threads switch between 
building different boards. Right now what happens on a thread (even 
without mrproper) is:

Thread 1 builds board X
- Some stuff gets rebuilt due to source changes
Thread 1 builds board Y
- Some stuff gets rebuilt due to source changes
- Lots more gets rebuilt because the config changed between the two 
boards, and bother were built in the same directory.

After this patch, the following happens:

Thread 1 builds board X
- Some stuff gets rebuilt due to source changes
Thread 1 builds board Y
- Some stuff gets rebuilt due to source changes
- Nothing else gets rebuilt

If the source changes happen not to affect some particular board, then 
this new scheme means that nothing gets rebuilt for it, which 
significantly speeds up the build.

>>> This commit adds two command-line options to implement the changes
>>> described above; -I ("--incremental") turns of "make mrproper" and -P
>>> ("--per-board-out-dir") creats a build directory per board rather than per
>>> thread.
>>>
>>> Tested:
>>>
>>>      ./tools/buildman/buildman.py tegra
>>>      ./tools/buildman/buildman.py -I -P tegra
>>>      ./tools/buildman/buildman.py -b tegra_dev tegra
>>>      ./tools/buildman/buildman.py -b tegra_dev -I -P tegra
>>>
>>> ... each once after deleting the buildman result/work directory, and once
>>> "incrementally" after a previous identical invocation.
>>>
>>> Signed-off-by: Stephen Warren <swarren@nvidia.com>
>>
>> Reviewed-by: Tom Rini <trini@konsulko.com>
>
> Oddly I don't seem to have received the original email, but I see this
> in patchwork.
>
> For me this cut build time from 6.25 minutes to 4.75 minutes for 30
> commits built for two similar boards on a 8-thread machine. It's a big
> win. I haven't yet tried it out on a a big build.
>
> Stephen can you please update the README with some of your commit
> notes? Other than that:
>
> Acked-by: Simon Glass <sjg@chromium.org>
> Tested-by: Simon Glass <sjg@chromium.org>
Simon Glass April 11, 2016, 4:58 p.m. UTC | #4
Hi Stephen,

On 11 April 2016 at 10:15, Stephen Warren <swarren@wwwdotorg.org> wrote:
> On 04/11/2016 06:04 AM, Simon Glass wrote:
>>
>> Hi,
>>
>> On 1 April 2016 at 07:35, Tom Rini <trini@konsulko.com> wrote:
>>>
>>> On Mon, Mar 28, 2016 at 04:39:35PM -0600, Stephen Warren wrote:
>>>
>>>> From: Stephen Warren <swarren@nvidia.com>
>>>>
>>>> One use-case for buildman is to continually run it interactively after
>>>> each small step in a large refactoring operation. This gives more
>>>> immediate feedback than making a number of commits and then going back
>>>> and
>>>> testing them. For this to work well, buildman needs to be extremely
>>>> fast.
>>>> At present, a couple issues prevent it being as fast as it could be:
>>>>
>>>> 1) Each time buildman runs "make %_defconfig", it runs "make mrproper"
>>>> first. This throws away all previous build results, requiring a
>>>> from-scratch build. Optionally avoiding this would speed up the build,
>>>> at
>>>> the cost of potentially causing or missing some build issues.
>>
>>
>> Does it actually cause problems? I think I have seen things go wrong
>> before, which is why I added it, but I can't remember the detail.
>
>
> I have not noticed any issues using -I and hence skipping the mrproper. It
> is possible you added the mrproper before the conversion to Kbuild; Kbuild
> seems to handle rebuilding after code changes much better than the old
> system, which definitely had some flaws that prevented incremental builds in
> many cases.

OK I see. For next release we should consider making -I the default.
>
>> Threads work by configuring and building the first commit for a board,
>> then incrementally building each commit for that same board. So it
>> seems like change should only be useful when all the boards you are
>> building have a similar config.
>
>
> No, because of point 2 below.
>
>>>> 2) A build tree is created per thread rather than per board. When a
>>>> thread
>>>> switches between building different boards, this often causes many files
>>>> to be rebuilt due to changing config options. Using a separate build
>>>> tree
>>>> for each board would avoid this. This does put more strain on the
>>>> system's
>>>> disk cache, but it is worth it on my system at least.
>>
>>
>> I'm not sure why this is a win, given what I said above. I'm starting
>> to feel that I don't understand what is going on.
>
>
> Since each board gets built in a separate directory, there's no build churn
> due to the configuration being changed when threads switch between building
> different boards. Right now what happens on a thread (even without mrproper)
> is:
>
> Thread 1 builds board X
> - Some stuff gets rebuilt due to source changes
> Thread 1 builds board Y
> - Some stuff gets rebuilt due to source changes
> - Lots more gets rebuilt because the config changed between the two boards,
> and bother were built in the same directory.
>
> After this patch, the following happens:
>
> Thread 1 builds board X
> - Some stuff gets rebuilt due to source changes
> Thread 1 builds board Y
> - Some stuff gets rebuilt due to source changes
> - Nothing else gets rebuilt
>
> If the source changes happen not to affect some particular board, then this
> new scheme means that nothing gets rebuilt for it, which significantly
> speeds up the build.

I understand what you are saying, but the intention of
builderthread.RunJob is to avoid this churn. It runs in a loop through
all commits, building the same board for each. What am I missing?

>
>
>>>> This commit adds two command-line options to implement the changes
>>>> described above; -I ("--incremental") turns of "make mrproper" and -P
>>>> ("--per-board-out-dir") creats a build directory per board rather than
>>>> per
>>>> thread.
>>>>
>>>> Tested:
>>>>
>>>>      ./tools/buildman/buildman.py tegra
>>>>      ./tools/buildman/buildman.py -I -P tegra
>>>>      ./tools/buildman/buildman.py -b tegra_dev tegra
>>>>      ./tools/buildman/buildman.py -b tegra_dev -I -P tegra
>>>>
>>>> ... each once after deleting the buildman result/work directory, and
>>>> once
>>>> "incrementally" after a previous identical invocation.
>>>>
>>>> Signed-off-by: Stephen Warren <swarren@nvidia.com>
>>>
>>>
>>> Reviewed-by: Tom Rini <trini@konsulko.com>
>>
>>
>> Oddly I don't seem to have received the original email, but I see this
>> in patchwork.
>>
>> For me this cut build time from 6.25 minutes to 4.75 minutes for 30
>> commits built for two similar boards on a 8-thread machine. It's a big
>> win. I haven't yet tried it out on a a big build.
>>
>> Stephen can you please update the README with some of your commit
>> notes? Other than that:
>>
>> Acked-by: Simon Glass <sjg@chromium.org>
>> Tested-by: Simon Glass <sjg@chromium.org>
>
>

Regards,
Simon
Stephen Warren April 11, 2016, 5:07 p.m. UTC | #5
On 04/11/2016 10:58 AM, Simon Glass wrote:
> Hi Stephen,
>
> On 11 April 2016 at 10:15, Stephen Warren <swarren@wwwdotorg.org> wrote:
>> On 04/11/2016 06:04 AM, Simon Glass wrote:
>>>
>>> Hi,
>>>
>>> On 1 April 2016 at 07:35, Tom Rini <trini@konsulko.com> wrote:
>>>>
>>>> On Mon, Mar 28, 2016 at 04:39:35PM -0600, Stephen Warren wrote:
>>>>
>>>>> From: Stephen Warren <swarren@nvidia.com>
>>>>>
>>>>> One use-case for buildman is to continually run it interactively after
>>>>> each small step in a large refactoring operation. This gives more
>>>>> immediate feedback than making a number of commits and then going back
>>>>> and
>>>>> testing them. For this to work well, buildman needs to be extremely
>>>>> fast.
>>>>> At present, a couple issues prevent it being as fast as it could be:
>>>>>
>>>>> 1) Each time buildman runs "make %_defconfig", it runs "make mrproper"
>>>>> first. This throws away all previous build results, requiring a
>>>>> from-scratch build. Optionally avoiding this would speed up the build,
>>>>> at
>>>>> the cost of potentially causing or missing some build issues.
>>>
>>>
>>> Does it actually cause problems? I think I have seen things go wrong
>>> before, which is why I added it, but I can't remember the detail.
>>
>>
>> I have not noticed any issues using -I and hence skipping the mrproper. It
>> is possible you added the mrproper before the conversion to Kbuild; Kbuild
>> seems to handle rebuilding after code changes much better than the old
>> system, which definitely had some flaws that prevented incremental builds in
>> many cases.
>
> OK I see. For next release we should consider making -I the default.
>>
>>> Threads work by configuring and building the first commit for a board,
>>> then incrementally building each commit for that same board. So it
>>> seems like change should only be useful when all the boards you are
>>> building have a similar config.
>>
>>
>> No, because of point 2 below.
>>
>>>>> 2) A build tree is created per thread rather than per board. When a
>>>>> thread
>>>>> switches between building different boards, this often causes many files
>>>>> to be rebuilt due to changing config options. Using a separate build
>>>>> tree
>>>>> for each board would avoid this. This does put more strain on the
>>>>> system's
>>>>> disk cache, but it is worth it on my system at least.
>>>
>>>
>>> I'm not sure why this is a win, given what I said above. I'm starting
>>> to feel that I don't understand what is going on.
>>
>>
>> Since each board gets built in a separate directory, there's no build churn
>> due to the configuration being changed when threads switch between building
>> different boards. Right now what happens on a thread (even without mrproper)
>> is:
>>
>> Thread 1 builds board X
>> - Some stuff gets rebuilt due to source changes
>> Thread 1 builds board Y
>> - Some stuff gets rebuilt due to source changes
>> - Lots more gets rebuilt because the config changed between the two boards,
>> and bother were built in the same directory.
>>
>> After this patch, the following happens:
>>
>> Thread 1 builds board X
>> - Some stuff gets rebuilt due to source changes
>> Thread 1 builds board Y
>> - Some stuff gets rebuilt due to source changes
>> - Nothing else gets rebuilt
>>
>> If the source changes happen not to affect some particular board, then this
>> new scheme means that nothing gets rebuilt for it, which significantly
>> speeds up the build.
>
> I understand what you are saying, but the intention of
> builderthread.RunJob is to avoid this churn. It runs in a loop through
> all commits, building the same board for each. What am I missing?

Each thread can build multiple boards in turn. This is not a problem 
building a series of commits for a board, but rather a problem when 
building multiple boards in turn. Also, see V2 of the patch which adds a 
longer explanation to the README.
diff mbox

Patch

diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py
index 141bf6469136..8ec355172901 100644
--- a/tools/buildman/builder.py
+++ b/tools/buildman/builder.py
@@ -205,7 +205,8 @@  class Builder:
 
     def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
                  gnu_make='make', checkout=True, show_unknown=True, step=1,
-                 no_subdirs=False, full_path=False, verbose_build=False):
+                 no_subdirs=False, full_path=False, verbose_build=False,
+                 incremental=False, per_board_out_dir=False):
         """Create a new Builder object
 
         Args:
@@ -224,6 +225,10 @@  class Builder:
             full_path: Return the full path in CROSS_COMPILE and don't set
                 PATH
             verbose_build: Run build with V=1 and don't use 'make -s'
+            incremental: Always perform incremental builds; don't run make
+                mrproper when configuring
+            per_board_out_dir: Build in a separate persistent directory per
+                board rather than a thread-specific directory
         """
         self.toolchains = toolchains
         self.base_dir = base_dir
@@ -263,7 +268,8 @@  class Builder:
         self.queue = Queue.Queue()
         self.out_queue = Queue.Queue()
         for i in range(self.num_threads):
-            t = builderthread.BuilderThread(self, i)
+            t = builderthread.BuilderThread(self, i, incremental,
+                    per_board_out_dir)
             t.setDaemon(True)
             t.start()
             self.threads.append(t)
diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py
index cf25bb8f1a7e..c512d3b521f7 100644
--- a/tools/buildman/builderthread.py
+++ b/tools/buildman/builderthread.py
@@ -80,11 +80,13 @@  class BuilderThread(threading.Thread):
         thread_num: Our thread number (0-n-1), used to decide on a
                 temporary directory
     """
-    def __init__(self, builder, thread_num):
+    def __init__(self, builder, thread_num, incremental, per_board_out_dir):
         """Set up a new builder thread"""
         threading.Thread.__init__(self)
         self.builder = builder
         self.thread_num = thread_num
+        self.incremental = incremental
+        self.per_board_out_dir = per_board_out_dir
 
     def Make(self, commit, brd, stage, cwd, *args, **kwargs):
         """Run 'make' on a particular commit and board.
@@ -136,7 +138,11 @@  class BuilderThread(threading.Thread):
         if self.builder.in_tree:
             out_dir = work_dir
         else:
-            out_dir = os.path.join(work_dir, 'build')
+            if self.per_board_out_dir:
+                out_rel_dir = os.path.join('..', brd.target)
+            else:
+                out_rel_dir = 'build'
+            out_dir = os.path.join(work_dir, out_rel_dir)
 
         # Check if the job was already completed last time
         done_file = self.builder.GetDoneFile(commit_upto, brd.target)
@@ -197,12 +203,12 @@  class BuilderThread(threading.Thread):
                         #
                         # Symlinks can confuse U-Boot's Makefile since
                         # we may use '..' in our path, so remove them.
-                        work_dir = os.path.realpath(work_dir)
-                        args.append('O=%s/build' % work_dir)
+                        out_dir = os.path.realpath(out_dir)
+                        args.append('O=%s' % out_dir)
                         cwd = None
                         src_dir = os.getcwd()
                     else:
-                        args.append('O=build')
+                        args.append('O=%s' % out_rel_dir)
                 if self.builder.verbose_build:
                     args.append('V=1')
                 else:
@@ -215,9 +221,11 @@  class BuilderThread(threading.Thread):
 
                 # If we need to reconfigure, do that now
                 if do_config:
-                    result = self.Make(commit, brd, 'mrproper', cwd,
-                            'mrproper', *args, env=env)
-                    config_out = result.combined
+                    config_out = ''
+                    if not self.incremental:
+                        result = self.Make(commit, brd, 'mrproper', cwd,
+                                'mrproper', *args, env=env)
+                        config_out += result.combined
                     result = self.Make(commit, brd, 'config', cwd,
                             *(args + config_args), env=env)
                     config_out += result.combined
diff --git a/tools/buildman/cmdline.py b/tools/buildman/cmdline.py
index 8341ab145cfa..3e3bd63e32c7 100644
--- a/tools/buildman/cmdline.py
+++ b/tools/buildman/cmdline.py
@@ -49,6 +49,8 @@  def ParseArgs():
     parser.add_option('-i', '--in-tree', dest='in_tree',
           action='store_true', default=False,
           help='Build in the source tree instead of a separate directory')
+    parser.add_option('-I', '--incremental', action='store_true',
+          default=False, help='Do not run make mrproper (when reconfiguring)')
     parser.add_option('-j', '--jobs', dest='jobs', type='int',
           default=None, help='Number of jobs to run at once (passed to make)')
     parser.add_option('-k', '--keep-outputs', action='store_true',
@@ -70,6 +72,8 @@  def ParseArgs():
           default=False, help='Do a rough build, with limited warning resolution')
     parser.add_option('-p', '--full-path', action='store_true',
           default=False, help="Use full toolchain path in CROSS_COMPILE")
+    parser.add_option('-P', '--per-board-out-dir', action='store_true',
+          default=False, help="Use an O= (output) directory per board rather than per thread")
     parser.add_option('-s', '--summary', action='store_true',
           default=False, help='Show a build summary')
     parser.add_option('-S', '--show-sizes', action='store_true',
diff --git a/tools/buildman/control.py b/tools/buildman/control.py
index c2c54bf0e81b..aeb128a6a3e9 100644
--- a/tools/buildman/control.py
+++ b/tools/buildman/control.py
@@ -250,7 +250,9 @@  def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
             options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
             show_unknown=options.show_unknown, step=options.step,
             no_subdirs=options.no_subdirs, full_path=options.full_path,
-            verbose_build=options.verbose_build)
+            verbose_build=options.verbose_build,
+            incremental=options.incremental,
+            per_board_out_dir=options.per_board_out_dir,)
     builder.force_config_on_failure = not options.quick
     if make_func:
         builder.do_make = make_func