[llvm-commits] [PATCH] [Lit] Use multiprocessing instead of threading

NAKAMURA Takumi geek4civic at gmail.com
Fri Nov 23 00:03:34 PST 2012


Hi chandlerc, ddunbar,

Threading is too slow possibly due to GIL.
multiprocessing brings 95% of processor usage. I believe it would scale up!


* For me with lit -j8 llvm/test clang/test :

AMD FX-8150 8 cores, 3.6GHz (same as bb.pgr.jp's buildslave)

- threading (trunk)

  50s to 59s (unstable), with -sv
  48s to 58s (unstable), with -q

- multiprocessing
 
  36s -sv
  35s -q


* Considerations:

  - Need the switch "--enable(disable)-mp"?
  - MP is disabled on Win32.
    - MP cannot be invoked via utils/llvm-lit. Investigating.
    - Starting MP's subprocess takes several seconds, due to pickling instead of fork().

http://llvm-reviews.chandlerc.com/D134

Files:
  llvm/utils/lit/lit/Test.py
  llvm/utils/lit/lit/main.py

Index: llvm/utils/lit/lit/Test.py
===================================================================
--- llvm/utils/lit/lit/Test.py
+++ llvm/utils/lit/lit/Test.py
@@ -7,12 +7,14 @@
         self.name = name
         self.isFailure = isFailure
 
-PASS        = TestResult('PASS', False)
-XFAIL       = TestResult('XFAIL', False)
-FAIL        = TestResult('FAIL', True)
-XPASS       = TestResult('XPASS', True)
-UNRESOLVED  = TestResult('UNRESOLVED', True)
-UNSUPPORTED = TestResult('UNSUPPORTED', False)
+TestResults = dict()
+
+PASS        = TestResults['PASS']        = TestResult('PASS',        False)
+XFAIL       = TestResults['XFAIL']       = TestResult('XFAIL',       False)
+FAIL        = TestResults['FAIL']        = TestResult('FAIL',        True)
+XPASS       = TestResults['XPASS']       = TestResult('XPASS',       True)
+UNRESOLVED  = TestResults['UNRESOLVED']  = TestResult('UNRESOLVED',  True)
+UNSUPPORTED = TestResults['UNSUPPORTED'] = TestResult('UNSUPPORTED', False)
 
 # Test classes.
 
Index: llvm/utils/lit/lit/main.py
===================================================================
--- llvm/utils/lit/lit/main.py
+++ llvm/utils/lit/lit/main.py
@@ -112,7 +112,9 @@
             item = self.provider.get()
             if item is None:
                 break
-            self.runTest(item)
+            result, output, elapsed = self.runTest(item)
+            item.setResult(result, output, elapsed)
+            self.display.update(item)
 
     def runTest(self, test):
         result = None
@@ -134,8 +136,22 @@
             output += '\n'
         elapsed = time.time() - startTime
 
-        test.setResult(result, output, elapsed)
-        self.display.update(test)
+        return ((result, output, elapsed))
+
+# It should not be @staticmethod for pickling on Win32.
+def runMP(litConfig, tests, testids, results):
+    tester = Tester(litConfig, None, None)
+    while True:
+        n = testids.get()
+        if n is None:
+            break
+        result, output, elapsed = tester.runTest(tests[n])
+
+        # Don't pass Test.*(s) into Queue. They might become non-identical
+        # objects against parent's context. Pass its name instead.
+        results.put((n, result.name, output, elapsed))
+
+    results.put((None, None, None, None))
 
 def dirContainsTestSuite(path):
     cfgpath = os.path.join(path, gSiteConfigName)
@@ -297,15 +313,60 @@
         if sub_ts and not N:
             litConfig.warning('test suite %r contained no tests' % sub_ts.name)
 
-def runTests(numThreads, litConfig, provider, display):
+def runMPTests(numThreads, litConfig, tests, display):
+
+    import multiprocessing
+
+    testids = multiprocessing.Queue()
+    results = multiprocessing.Queue()
+    ps = [multiprocessing.Process(
+            target = runMP,
+            args = (litConfig, tests, testids, results),
+            )
+          for i in range(numThreads)]
+    for p in ps:
+        p.start();
+
+    for n in range(len(tests)):
+        testids.put(n)
+
+    for i in range(numThreads):
+        for test in range(numThreads):
+            testids.put(None)
+        while True:
+            n, resultname, output, elapsed = results.get()
+            if n is None:
+                break
+            result = Test.TestResults[resultname]
+            test = tests[n]
+            test.setResult(result, output, elapsed)
+            display.update(test)
+
+    for p in ps:
+        p.join();
+
+def runTests(numThreads, litConfig, tests, maxTime, display):
     # If only using one testing thread, don't use threads at all; this lets us
     # profile, among other things.
     if numThreads == 1:
+        provider = TestProvider(tests, maxTime)
         t = Tester(litConfig, provider, display)
         t.run()
         return
 
+    # Try to run MPTests.
+    try:
+        if platform.system()=='Windows':
+            # It's still slow to invoke children due to cost of pickling.
+            pass
+        else:
+            runMPTests(numThreads, litConfig, tests, display)
+            return
+    except ImportError:
+        pass
+
     # Otherwise spin up the testing threads and wait for them to finish.
+    provider = TestProvider(tests, maxTime)
     testers = [Tester(litConfig, provider, display)
                for i in range(numThreads)]
     for t in testers:
@@ -594,8 +655,7 @@
 
     startTime = time.time()
     display = TestingProgressDisplay(opts, len(tests), progressBar)
-    provider = TestProvider(tests, opts.maxTime)
-    runTests(opts.numThreads, litConfig, provider, display)
+    runTests(opts.numThreads, litConfig, tests, opts.maxTime, display)
     display.finish()
 
     if not opts.quiet:
-------------- next part --------------
A non-text attachment was scrubbed...
Name: D134.1.patch
Type: text/x-patch
Size: 4690 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20121123/d8b20690/attachment.bin>


More information about the llvm-commits mailing list