Performance improvement: cut down on Proxy.__getattr__ calls.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 6 Jan 2003 03:54:25 +0000 (03:54 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 6 Jan 2003 03:54:25 +0000 (03:54 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@535 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Environment.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Sig/SigTests.py
src/engine/SCons/Sig/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py

index 7cb4eb926f6a06235d39cb715613c6c28efce72c..88ee9e86962e2a4f456ff6f110010a70ad37ee06 100644 (file)
@@ -31,6 +31,9 @@ RELEASE 0.10 - XXX
   - Fix SCons not exiting with the appropriate status on build errors
     (and probably in other situations).
 
+  - Significant performance improvement from using a more efficient
+    check, throughout the code, for whether a Node has a Builder.
+
   From Steve Leblanc:
 
   - Add a Clean() method to support removing user-specified targets
index 2f5df7f19bd7f69a6e6f05d8be33c202b0f080cd..15358c1cc947de0eef88ef79280a6492204f5f3c 100644 (file)
@@ -149,7 +149,7 @@ def _init_nodes(builder, env, overrides, tlist, slist):
     for t in tlist:
         if t.side_effect:
             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
-        if t.builder is not None:
+        if t.has_builder():
             if t.env != env:
                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
             elif t.overrides != overrides:
index 5906314b5bf083b07956983f84ba271af4e79864..9905eb90607a6a253012d3b0a6521791022f5e49 100644 (file)
@@ -116,6 +116,8 @@ class BuilderTestCase(unittest.TestCase):
                 return self.name
             def builder_set(self, builder):
                 self.builder = builder
+            def has_builder(self):
+                return not self.builder is None
             def env_set(self, env, safe=0):
                 self.env = env
             def add_source(self, source):
index 8861a99e5d2528d0705b0a678fa7b860bcf5b246..a5672aefefaf74b6db5b4b4effb73245081f6e48 100644 (file)
@@ -360,9 +360,9 @@ class Environment:
 
         for side_effect in side_effects:
             # A builder of 1 means the node is supposed to appear
-           # buildable without actually having a builder, so we allow
-           # it to be a side effect as well.
-            if side_effect.builder is not None and side_effect.builder != 1:
+            # buildable without actually having a builder, so we allow
+            # it to be a side effect as well.
+            if side_effect.has_builder() and side_effect.builder != 1:
                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
             side_effect.add_source(targets)
             side_effect.side_effect = 1
index 0569deaebcb595669a1c2d48bb40e02526a81c46..826307b1d3fd5d3c9c64314a44f9e76c7c00ee64 100644 (file)
@@ -182,7 +182,7 @@ class Entry(SCons.Node.Node):
 
     def __str__(self):
         """A FS node's string representation is its path name."""
-        if self.duplicate or self.builder:
+        if self.duplicate or self.has_builder():
             return self.path
         return self.srcnode().path
 
@@ -513,7 +513,7 @@ class FS:
                        # This is usually the case with BuildDir().
                        # We only want to find pre-existing files.
                         if rnode.exists() and \
-                           (isinstance(rnode, Dir) or not rnode.builder):
+                           (isinstance(rnode, Dir) or not rnode.has_builder()):
                             return rnode
                     except TypeError:
                         pass # Wrong type of node.
@@ -558,7 +558,7 @@ class FS:
                             # want it to show up in the build tree.  This is usually the
                             # case with BuildDir().    We only want to find pre-existing files.
                             if (not must_exist or rnode.exists()) and \
-                               (not rnode.builder or isinstance(rnode, Dir)):
+                               (not rnode.has_builder() or isinstance(rnode, Dir)):
                                 ret.append(rnode)
                         except TypeError:
                             pass # Wrong type of node.
@@ -819,7 +819,7 @@ class File(Entry):
         in the .sconsign file.
         """
 
-        if self.builder:
+        if self.has_builder():
             if SCons.Sig.build_signature:
                 if not hasattr(self, 'bsig'):
                     self.set_bsig(calc.bsig(self.rfile()))
@@ -904,14 +904,14 @@ class File(Entry):
         """Prepare for this file to be created."""
 
         def missing(node):
-            return not node.builder and not node.linked and not node.rexists()
+            return not node.has_builder() and not node.linked and not node.rexists()
         missing_sources = filter(missing, self.children())
         if missing_sources:
             desc = "No Builder for target `%s', needed by `%s'." % (missing_sources[0], self)
             raise SCons.Errors.StopError, desc
 
         if self.exists():
-            if self.builder and not self.precious:
+            if self.has_builder() and not self.precious:
                 Unlink(self, None, None)
                 if hasattr(self, '_exists'):
                     delattr(self, '_exists')
@@ -931,7 +931,7 @@ class File(Entry):
 
     def exists(self):
         # Duplicate from source path if we are set up to do this.
-        if self.duplicate and not self.builder and not self.linked:
+        if self.duplicate and not self.has_builder() and not self.linked:
             src=self.srcnode().rfile()
             if src.exists() and src.abspath != self.abspath:
                 self._createDir()
@@ -1008,7 +1008,7 @@ def find_file(filename, paths, node_factory = default_fs.File):
         try:
             node = node_factory(filename, dir)
             # Return true of the node exists or is a derived node.
-            if node.builder or \
+            if node.has_builder() or \
                (isinstance(node, SCons.Node.FS.Entry) and node.exists()):
                 retval = node
                 break
index e5aae5c1c2ef534eb8ee281bcbacf0bb5c42bf02..3bafb9cce1bf3b0c1746b218aa4ba19252280007 100644 (file)
@@ -114,7 +114,7 @@ class Node:
         so only do thread safe stuff here. Do thread unsafe stuff in
         built().
         """
-        if not self.builder:
+        if not self.has_builder():
             return None
         action_list = self.builder.get_actions()
         if not action_list:
@@ -160,6 +160,19 @@ class Node:
     def builder_set(self, builder):
         self.builder = builder
 
+    def has_builder(self):
+        """Return whether this Node has a builder or not.
+
+        In Boolean tests, this turns out to be a *lot* more efficient
+        than simply examining the builder attribute directly ("if
+        node.builder: ..."). When the builder attribute is examined
+        directly, it ends up calling __getattr__ for both the __len__
+        and __nonzero__ attributes on instances of our Builder Proxy
+        class(es), generating a bazillion extra calls and slowing
+        things down immensely.
+        """
+        return not self.builder is None
+
     def builder_sig_adapter(self):
         """Create an adapter for calculating a builder's signature.
 
@@ -190,7 +203,7 @@ class Node:
         if not self.implicit is None:
             return
         self.implicit = []
-        if not self.builder:
+        if not self.has_builder():
             return
 
         if implicit_cache and not implicit_deps_changed:
@@ -243,7 +256,7 @@ class Node:
         in the .sconsign file.
         """
 
-        if self.builder:
+        if self.has_builder():
             if SCons.Sig.build_signature:
                 if not hasattr(self, 'bsig'):
                     self.set_bsig(calc.bsig(self))
index 93f4795e843051fe1d9192f0727245e659e5aad9..ca6536fbeddd645c005a01153b7dd68c6b81e369 100644 (file)
@@ -75,9 +75,9 @@ class BuildTask(SCons.Taskmaster.Task):
     def execute(self):
         target = self.targets[0]
         if target.get_state() == SCons.Node.up_to_date:
-            if self.top and target.builder:
+            if self.top and target.has_builder():
                 display('scons: "%s" is up to date.' % str(target))
-        elif target.builder and not hasattr(target.builder, 'status'):
+        elif target.has_builder() and not hasattr(target.builder, 'status'):
             if print_time:
                 start_time = time.time()
             SCons.Taskmaster.Task.execute(self)
@@ -100,7 +100,7 @@ class BuildTask(SCons.Taskmaster.Task):
             
     def executed(self):
         t = self.targets[0]
-        if self.top and not t.builder and not t.side_effect:
+        if self.top and not t.has_builder() and not t.side_effect:
             if not t.exists():
                 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
                 if not keep_going_on_error:
@@ -145,7 +145,7 @@ class BuildTask(SCons.Taskmaster.Task):
 class CleanTask(SCons.Taskmaster.Task):
     """An SCons clean task."""
     def show(self):
-        if self.targets[0].builder or self.targets[0].side_effect:
+        if self.targets[0].has_builder() or self.targets[0].side_effect:
             display("Removed " + str(self.targets[0]))
         if SCons.Script.SConscript.clean_targets.has_key(str(self.targets[0])):
             files = SCons.Script.SConscript.clean_targets[str(self.targets[0])]
@@ -153,7 +153,7 @@ class CleanTask(SCons.Taskmaster.Task):
                 SCons.Utils.fs_delete(str(f), 0)
 
     def remove(self):
-        if self.targets[0].builder or self.targets[0].side_effect:
+        if self.targets[0].has_builder() or self.targets[0].side_effect:
             for t in self.targets:
                 try:
                     removed = t.remove()
@@ -217,7 +217,7 @@ def get_all_children(node): return node.all_children(None)
 
 def get_derived_children(node):
     children = node.all_children(None)
-    return filter(lambda x: x.builder, children)
+    return filter(lambda x: x.has_builder(), children)
 
 def _scons_syntax_error(e):
     """Handle syntax errors. Print out a message and show where the error
index ccd58976260eadbbf016900f79546c3d7cc030de..57c5a51866ef696b10b3edbc3feb6e14d18ba0fa 100644 (file)
@@ -59,6 +59,9 @@ class DummyNode:
         self.oldbsig = 0
         self.oldcsig = 0
 
+    def has_builder(self):
+        return self.builder
+
     def get_contents(self):
         # a file that doesn't exist has no contents:
         assert self.exists()
@@ -97,7 +100,7 @@ class DummyNode:
         return None
 
     def calc_signature(self, calc):
-        if self.builder:
+        if self.has_builder():
             return calc.bsig(self)
         else:
             return calc.csig(self)
@@ -212,7 +215,7 @@ class SigTestBase:
 
         for node in nodes:
             self.failUnless(not current(calc, node),
-                            "none of the nodes should be current")
+                            "node %s should not be current" % node.path)
 
         # simulate a build:
         self.files[1].modify('built', 222)
@@ -230,7 +233,7 @@ class SigTestBase:
 
         for node in nodes:
             self.failUnless(current(calc, node),
-                            "all of the nodes should be current")
+                            "node %s should be current" % node.path)
 
     def test_modify(self):
 
@@ -278,7 +281,7 @@ class SigTestBase:
 
         for node in nodes:
             self.failUnless(current(calc, node),
-                            "all of the nodes should be current")
+                            "node %s should be current" % node.path)
 
 
 class MD5TestCase(unittest.TestCase, SigTestBase):
@@ -311,6 +314,8 @@ class CalcTestCase(unittest.TestCase):
                 self.ignore = []
                 self.builder = None
                 self.use_signature = 1
+            def has_builder(self):
+                return not self.builder is None
             def children(self):
                 return filter(lambda x, i=self.ignore: x not in i, self.kids)
             def all_children(self):
index 5de89fa7ae9ab51c5a8761b2f84cb6b4318c3b8b..4fe94231c814b376490f7e18fdbdf9869dddb35a 100644 (file)
@@ -263,7 +263,7 @@ class Calculator:
         what's wanted.
         """
         sigs = map(lambda n, c=self: n.calc_signature(c), node.children())
-        if node.builder:
+        if node.has_builder():
             sigs.append(self.module.signature(node.builder_sig_adapter()))
 
         bsig = self.module.collect(filter(lambda x: not x is None, sigs))
@@ -320,7 +320,7 @@ class Calculator:
         """
         oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
 
-        if not node.builder and node.get_timestamp() == oldtime:
+        if not node.has_builder() and node.get_timestamp() == oldtime:
             return 1
 
         return self.module.current(newsig, oldbsig)
index eec50bf3afa9f651fa6f9d2ef73fd9d6c05c7a9a..74703f2b68b57db9e81714a1e7e5f6b78a861d59 100644 (file)
@@ -213,7 +213,7 @@ class Taskmaster:
             # Add non-derived files that have not been built
             # to the candidates list:
             def derived(node):
-                return (node.builder or node.side_effect) and node.get_state() == None
+                return (node.has_builder() or node.side_effect) and node.get_state() == None
             derived = filter(derived, children)
             if derived:
                 derived.reverse()
index d8bcc4fd4095f022e9099b4eaec96a72ce95b708..79ff11bfb5535290ab389a4f2b7b57852927f0ae 100644 (file)
@@ -57,6 +57,9 @@ class Node:
         global built_text
         built_text = self.name + " built"
 
+    def has_builder(self):
+        return not self.builder is None
+
     def built(self):
         global built_text
         built_text = built_text + " really"