Bug 867903. Speed up getDescriptors and some other obvious slow points in codegen. r=khuey
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 09 May 2013 13:06:48 -0400
changeset 131377 59c279d93f8e268449e5153b1330e9d254ea4f8a
parent 131376 712d3684efe44cff7d6963ba3d8af0d58e80f802
child 131378 52f646b2055c3af052d40060a85bbca741622687
push id27833
push userbzbarsky@mozilla.com
push dateThu, 09 May 2013 17:09:24 +0000
treeherdermozilla-inbound@013fd226403e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs867903
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 867903. Speed up getDescriptors and some other obvious slow points in codegen. r=khuey
dom/bindings/BindingGen.py
dom/bindings/Configuration.py
dom/bindings/ExampleGen.py
dom/bindings/GlobalGen.py
--- a/dom/bindings/BindingGen.py
+++ b/dom/bindings/BindingGen.py
@@ -30,24 +30,21 @@ def main():
     o = OptionParser(usage=usagestring)
     o.add_option("--verbose-errors", action='store_true', default=False,
                  help="When an error happens, display the Python traceback.")
     (options, args) = o.parse_args()
 
     configFile = os.path.normpath(args[0])
     srcPrefix = os.path.normpath(args[1])
 
-    # Load the parsing results
+    # Load the configuration
     f = open('ParserResults.pkl', 'rb')
-    parserData = cPickle.load(f)
+    config = cPickle.load(f)
     f.close()
 
-    # Create the configuration data.
-    config = Configuration(configFile, parserData)
-
     def readFile(f):
         file = open(f, 'rb')
         try:
             contents = file.read()
         finally:
             file.close()
         return contents
     allWebIDLFiles = readFile(args[2]).split()
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -55,16 +55,29 @@ class Configuration:
 
         # Mark the descriptors for which the nativeType corresponds to exactly
         # one interface.
         for descriptor in self.descriptors:
             descriptor.unsharedImplementation = all(
                 d.nativeType != descriptor.nativeType or d == descriptor
                 for d in self.descriptors)
 
+        # Keep the descriptor list sorted for determinism.
+        self.descriptors.sort(lambda x,y: cmp(x.name, y.name))
+
+        self.descriptorsByName = {}
+        for d in self.descriptors:
+            self.descriptorsByName.setdefault(d.interface.identifier.name,
+                                              []).append(d)
+
+        self.descriptorsByFile = {}
+        for d in self.descriptors:
+            self.descriptorsByFile.setdefault(d.interface.filename(),
+                                              []).append(d)
+
         self.enums = [e for e in parseData if e.isEnum()]
 
         # Figure out what our main-thread and worker dictionaries and callbacks
         # are.
         mainTypes = set()
         for descriptor in ([self.getDescriptor("DummyInterface", workers=False)] +
                            self.getDescriptors(workers=False, isExternal=False, skipGen=False)):
             mainTypes |= set(getFlatTypes(getTypesFromDescriptor(descriptor)))
@@ -85,27 +98,32 @@ class Configuration:
                 if item in main:
                     item.setUserData("mainThread", True)
                 if item in worker:
                     item.setUserData("workers", True)
         flagWorkerOrMainThread(self.dictionaries, mainDictionaries,
                                workerDictionaries);
         flagWorkerOrMainThread(self.callbacks, mainCallbacks, workerCallbacks)
 
-        # Keep the descriptor list sorted for determinism.
-        self.descriptors.sort(lambda x,y: cmp(x.name, y.name))
-
     def getInterface(self, ifname):
         return self.interfaces[ifname]
     def getDescriptors(self, **filters):
         """Gets the descriptors that match the given filters."""
         curr = self.descriptors
+        # Collect up our filters, because we may have a webIDLFile filter that
+        # we always want to apply first.
+        tofilter = []
         for key, val in filters.iteritems():
             if key == 'webIDLFile':
-                getter = lambda x: x.interface.filename()
+                # Special-case this part to make it fast, since most of our
+                # getDescriptors calls are conditioned on a webIDLFile.  We may
+                # not have this key, in which case we have no descriptors
+                # either.
+                curr = self.descriptorsByFile.get(val, [])
+                continue
             elif key == 'hasInterfaceObject':
                 getter = lambda x: (not x.interface.isExternal() and
                                     x.interface.hasInterfaceObject())
             elif key == 'hasInterfacePrototypeObject':
                 getter = lambda x: (not x.interface.isExternal() and
                                     x.interface.hasInterfacePrototypeObject())
             elif key == 'hasInterfaceOrInterfacePrototypeObject':
                 getter = lambda x: x.hasInterfaceOrInterfacePrototypeObject()
@@ -113,18 +131,22 @@ class Configuration:
                 getter = lambda x: x.interface.isCallback()
             elif key == 'isExternal':
                 getter = lambda x: x.interface.isExternal()
             elif key == 'isJSImplemented':
                 getter = lambda x: x.interface.isJSImplemented()
             elif key == 'isNavigatorProperty':
                 getter = lambda x: x.interface.getNavigatorProperty() != None
             else:
-                getter = lambda x: getattr(x, key)
-            curr = filter(lambda x: getter(x) == val, curr)
+                # Have to watch out: just closing over "key" is not enough,
+                # since we're about to mutate its value
+                getter = (lambda attrName: lambda x: getattr(x, attrName))(key)
+            tofilter.append((getter, val))
+        for f in tofilter:
+            curr = filter(lambda x: f[0](x) == f[1], curr)
         return curr
     def getEnums(self, webIDLFile):
         return filter(lambda e: e.filename() == webIDLFile, self.enums)
 
     @staticmethod
     def _filterForFileAndWorkers(items, filters):
         """Gets the items that match the given filters."""
         for key, val in filters.iteritems():
@@ -143,27 +165,21 @@ class Configuration:
     def getCallbacks(self, **filters):
         return self._filterForFileAndWorkers(self.callbacks, filters)
 
     def getDescriptor(self, interfaceName, workers):
         """
         Gets the appropriate descriptor for the given interface name
         and the given workers boolean.
         """
-        iface = self.getInterface(interfaceName)
-        descriptors = self.getDescriptors(interface=iface)
-
-        # The only filter we currently have is workers vs non-workers.
-        matches = filter(lambda x: x.workers is workers, descriptors)
+        for d in self.descriptorsByName[interfaceName]:
+            if d.workers == workers:
+                return d
 
-        # After filtering, we should have exactly one result.
-        if len(matches) is not 1:
-            raise NoSuchDescriptorError("For " + interfaceName + " found " +
-                                        str(len(matches)) + " matches");
-        return matches[0]
+        raise NoSuchDescriptorError("For " + interfaceName + " found no matches");
     def getDescriptorProvider(self, workers):
         """
         Gets a descriptor provider that can provide descriptors as needed,
         for the given workers boolean
         """
         return DescriptorProvider(self, workers)
 
 class NoSuchDescriptorError(TypeError):
--- a/dom/bindings/ExampleGen.py
+++ b/dom/bindings/ExampleGen.py
@@ -29,21 +29,18 @@ def main():
                  help="When an error happens, display the Python traceback.")
     (options, args) = o.parse_args()
 
     if len(args) != 2:
         o.error(usagestring)
     configFile = os.path.normpath(args[0])
     interfaceName = args[1]
 
-    # Load the parsing results
+    # Load the configuration
     f = open('ParserResults.pkl', 'rb')
-    parserData = cPickle.load(f)
+    config = cPickle.load(f)
     f.close()
 
-    # Create the configuration data.
-    config = Configuration(configFile, parserData)
-
     # Generate the example class.
     generate_interface_example(config, interfaceName)
 
 if __name__ == '__main__':
     main()
--- a/dom/bindings/GlobalGen.py
+++ b/dom/bindings/GlobalGen.py
@@ -50,24 +50,24 @@ def main():
     for filename in fileList:
         fullPath = os.path.normpath(os.path.join(baseDir, filename))
         f = open(fullPath, 'rb')
         lines = f.readlines()
         f.close()
         parser.parse(''.join(lines), fullPath)
     parserResults = parser.finish()
 
-    # Write the parser results out to a pickle.
-    resultsFile = open('ParserResults.pkl', 'wb')
-    cPickle.dump(parserResults, resultsFile, -1)
-    resultsFile.close()
-
     # Load the configuration.
     config = Configuration(configFile, parserResults)
 
+    # Write the configuration out to a pickle.
+    resultsFile = open('ParserResults.pkl', 'wb')
+    cPickle.dump(config, resultsFile, -1)
+    resultsFile.close()
+
     # Generate the prototype list.
     generate_file(config, 'PrototypeList', 'declare')
 
     # Generate the common code.
     generate_file(config, 'RegisterBindings', 'declare')
     generate_file(config, 'RegisterBindings', 'define')
 
     generate_file(config, 'UnionTypes', 'declare')