Commit 18138f26 authored by Leszek Swirski's avatar Leszek Swirski Committed by Commit Bot

[gcmole] Improve performance

Improve the performance of gcmole by

  * Precompiling the regexes in GCSuspectsCollector.Resolve
  * Merging those regexes into a single regex, using '|'
  * Changing multiprocess clang plugin invocation to threaded (running
    the plugin releases the GIL so this can efficiently thread). This
    uses a simple worker pool with a single work queue.
  * Change clang plugin invocation loop to yield after each invocation.
    This pipelines the dump-callees plugin and GCSuspectsCollector
    Parse/Resolve, so that the parse can happen while waiting for other
    callee dumps to finish.

Change-Id: Ib9fca70dbcfd2f9d1aebc8bd11aa1d1f7d34e24a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2562242Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Reviewed-by: 's avatarLiviu Rau <liviurau@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71617}
parent 31b23fcb
...@@ -11,11 +11,16 @@ from __future__ import print_function ...@@ -11,11 +11,16 @@ from __future__ import print_function
import collections import collections
import difflib import difflib
import multiprocessing from multiprocessing import cpu_count
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import threading
if sys.version_info.major > 2:
import queue
else:
import Queue as queue
ArchCfg = collections.namedtuple("ArchCfg", ArchCfg = collections.namedtuple("ArchCfg",
["triple", "arch_define", "arch_options"]) ["triple", "arch_define", "arch_options"])
...@@ -103,16 +108,34 @@ def MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir, ...@@ -103,16 +108,34 @@ def MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir,
def InvokeClangPluginForFile(filename, cmd_line, verbose): def InvokeClangPluginForFile(filename, cmd_line, verbose):
if verbose:
print("popen ", " ".join(cmd_line + [filename]))
p = subprocess.Popen(
cmd_line + [filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
return p.returncode, stdout, stderr
def InvokeClangPluginForFilesInQueue(i, input_queue, output_queue, cancel_event,
cmd_line, verbose):
success = False
try: try:
log("-- {}", filename) while not cancel_event.is_set():
if verbose: filename = input_queue.get_nowait()
print("popen ", " ".join(cmd_line + [filename])) ret, stdout, stderr = InvokeClangPluginForFile(filename, cmd_line,
p = subprocess.Popen(cmd_line + [filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) verbose)
stdout, stderr = p.communicate() output_queue.put_nowait((filename, ret, stdout.decode('utf-8'), stderr.decode('utf-8')))
return p.returncode, stdout, stderr if ret != 0:
break
except KeyboardInterrupt: except KeyboardInterrupt:
log("-- Interrupting {}", filename) log("-- [{}] Interrupting", i)
return 1, "" except queue.Empty:
success = True
finally:
# Emit a success bool so that the reader knows that there was either an
# error or all files were processed.
output_queue.put_nowait(success)
def InvokeClangPluginForEachFile( def InvokeClangPluginForEachFile(
filenames, filenames,
...@@ -126,48 +149,58 @@ def InvokeClangPluginForEachFile( ...@@ -126,48 +149,58 @@ def InvokeClangPluginForEachFile(
cmd_line = MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir, cmd_line = MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir,
clang_plugins_dir) clang_plugins_dir)
verbose = flags["verbose"] verbose = flags["verbose"]
outputs = {}
if flags["sequential"]: if flags["sequential"]:
log("** Sequential execution.") log("** Sequential execution.")
for filename in filenames: for filename in filenames:
returncode, stdout, stderr = InvokeClangPluginForFile(filename, cmd_line, verbose) log("-- {}", filename)
returncode, stdout, stderr = InvokeClangPluginForFile(
filename, cmd_line, verbose)
if returncode != 0: if returncode != 0:
sys.stderr.write(stderr) sys.stderr.write(stderr)
sys.exit(returncode) sys.exit(returncode)
outputs[filename] = (stdout, stderr) yield filename, stdout, stderr
else: else:
log("** Parallel execution.") log("** Parallel execution.")
cpus = multiprocessing.cpu_count() cpus = cpu_count()
pool = multiprocessing.Pool(cpus) input_queue = queue.Queue()
output_queue = queue.Queue()
threads = []
try: try:
# Track active invokes with a semaphore, to prevent submitting too many
# concurrent invokes to the pool.
execution_slots = multiprocessing.BoundedSemaphore(cpus)
async_outputs = {}
for filename in filenames: for filename in filenames:
execution_slots.acquire() input_queue.put(filename)
def callback(output):
execution_slots.release() cancel_event = threading.Event()
async_outputs[filename] = pool.apply_async( for i in range(min(len(filenames), cpus)):
InvokeClangPluginForFile, (filename, cmd_line, verbose), threads.append(
callback=callback) threading.Thread(
target=InvokeClangPluginForFilesInQueue,
for filename, output in async_outputs.items(): args=(i, input_queue, output_queue, cancel_event, cmd_line,
returncode, stdout, stderr = output.get() verbose)))
for t in threads:
t.start()
num_finished = 0
while num_finished < len(threads):
output = output_queue.get()
if type(output) == bool:
if output:
num_finished += 1
continue
else:
break
filename, returncode, stdout, stderr = output
log("-- {}", filename)
if returncode != 0: if returncode != 0:
sys.stderr.write(stderr) sys.stderr.write(stderr)
sys.exit(returncode) sys.exit(returncode)
outputs[filename] = (stdout, stderr) yield filename, stdout, stderr
except KeyboardInterrupt as e:
pool.terminate()
pool.join()
raise e
finally:
pool.close()
return outputs finally:
cancel_event.set()
for t in threads:
t.join()
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
...@@ -249,7 +282,7 @@ def FilesForTest(arch): ...@@ -249,7 +282,7 @@ def FilesForTest(arch):
# #
# This means that we can match just the function name by matching only # This means that we can match just the function name by matching only
# after a comma. # after a comma.
ALLOWLIST = set([ ALLOWLIST = [
# The following functions call CEntryStub which is always present. # The following functions call CEntryStub which is always present.
"MacroAssembler.*,CallRuntime", "MacroAssembler.*,CallRuntime",
"CompileCallLoadPropertyWithInterceptor", "CompileCallLoadPropertyWithInterceptor",
...@@ -270,7 +303,27 @@ ALLOWLIST = set([ ...@@ -270,7 +303,27 @@ ALLOWLIST = set([
# CodeCreateEvent receives AbstractCode (a raw ptr) as an argument. # CodeCreateEvent receives AbstractCode (a raw ptr) as an argument.
"CodeCreateEvent", "CodeCreateEvent",
"WriteField", "WriteField",
]) ]
GC_PATTERN = ",.*Collect.*Garbage"
SAFEPOINT_PATTERN = ",EnterSafepoint"
ALLOWLIST_PATTERN = "|".join("(?:%s)" % p for p in ALLOWLIST)
def MergeRegexp(pattern_dict):
return re.compile("|".join(
"(?P<%s>%s)" % (key, value) for (key, value) in pattern_dict.items()))
IS_SPECIAL_WITHOUT_ALLOW_LIST = MergeRegexp({
"gc": GC_PATTERN,
"safepoint": SAFEPOINT_PATTERN
})
IS_SPECIAL_WITH_ALLOW_LIST = MergeRegexp({
"gc": GC_PATTERN,
"safepoint": SAFEPOINT_PATTERN,
"allow": ALLOWLIST_PATTERN
})
class GCSuspectsCollector: class GCSuspectsCollector:
...@@ -279,8 +332,9 @@ class GCSuspectsCollector: ...@@ -279,8 +332,9 @@ class GCSuspectsCollector:
self.gc = {} self.gc = {}
self.gc_caused = collections.defaultdict(lambda: []) self.gc_caused = collections.defaultdict(lambda: [])
self.funcs = {} self.funcs = {}
self.current_scope = None self.current_caller = None
self.allowlist = flags["allowlist"] self.allowlist = flags["allowlist"]
self.is_special = IS_SPECIAL_WITH_ALLOW_LIST if self.allowlist else IS_SPECIAL_WITHOUT_ALLOW_LIST
def AddCause(self, name, cause): def AddCause(self, name, cause):
self.gc_caused[name].append(cause) self.gc_caused[name].append(cause)
...@@ -292,27 +346,25 @@ class GCSuspectsCollector: ...@@ -292,27 +346,25 @@ class GCSuspectsCollector:
if funcname[0] != "\t": if funcname[0] != "\t":
self.Resolve(funcname) self.Resolve(funcname)
self.current_scope = funcname self.current_caller = funcname
else: else:
name = funcname[1:] name = funcname[1:]
self.Resolve(name)[self.current_scope] = True callers_for_name = self.Resolve(name)
callers_for_name.add(self.current_caller)
def Resolve(self, name): def Resolve(self, name):
if name not in self.funcs: if name not in self.funcs:
self.funcs[name] = {} self.funcs[name] = set()
m = self.is_special.search(name)
if re.search(",.*Collect.*Garbage", name): if m:
self.gc[name] = True if m.group("gc"):
self.AddCause(name, "<GC>") self.gc[name] = True
self.AddCause(name, "<GC>")
if re.search(",EnterSafepoint", name): elif m.group("safepoint"):
self.gc[name] = True self.gc[name] = True
self.AddCause(name, "<Safepoint>") self.AddCause(name, "<Safepoint>")
elif m.group("allow"):
if self.allowlist: self.gc[name] = False
for allow in ALLOWLIST:
if re.search(allow, name):
self.gc[name] = False
return self.funcs[name] return self.funcs[name]
...@@ -338,10 +390,10 @@ def GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir, ...@@ -338,10 +390,10 @@ def GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir,
collector = GCSuspectsCollector(flags) collector = GCSuspectsCollector(flags)
log("** Building GC Suspects for {}", arch) log("** Building GC Suspects for {}", arch)
for filename, (stdout, stderr) in InvokeClangPluginForEachFile( for filename, stdout, stderr in InvokeClangPluginForEachFile(
files, "dump-callees", [], arch_cfg, flags, clang_bin_dir, files, "dump-callees", [], arch_cfg, flags, clang_bin_dir,
clang_plugins_dir).items(): clang_plugins_dir):
collector.Parse(stdout.split('\n')) collector.Parse(stdout.splitlines())
collector.Propagate() collector.Propagate()
...@@ -380,7 +432,6 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir, ...@@ -380,7 +432,6 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir,
else: else:
log("** Reusing GCSuspects for {}", arch) log("** Reusing GCSuspects for {}", arch)
processed_files = 0 processed_files = 0
errors_found = False errors_found = False
output = "" output = ""
...@@ -395,7 +446,7 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir, ...@@ -395,7 +446,7 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir,
plugin_args.append("--dead-vars") plugin_args.append("--dead-vars")
if flags["verbose_trace"]: if flags["verbose_trace"]:
plugin_args.append("--verbose") plugin_args.append("--verbose")
for filename, (stdout, stderr) in InvokeClangPluginForEachFile( for filename, stdout, stderr in InvokeClangPluginForEachFile(
files, files,
"find-problems", "find-problems",
plugin_args, plugin_args,
...@@ -403,16 +454,15 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir, ...@@ -403,16 +454,15 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir,
flags, flags,
clang_bin_dir, clang_bin_dir,
clang_plugins_dir, clang_plugins_dir,
).items(): ):
processed_files = processed_files + 1 processed_files = processed_files + 1
for l in stderr.split('\n'): if not errors_found:
if not errors_found: errors_found = re.search("^[^:]+:\d+:\d+: (warning|error)", stderr,
errors_found = re.match("^[^:]+:\d+:\d+: (warning|error)", re.MULTILINE) is not None
l) is not None if for_test:
if for_test: output = output + stderr
output = output + "\n" + l else:
else: sys.stdout.write(stderr)
print(l)
log( log(
"** Done processing {} files. {}", "** Done processing {} files. {}",
...@@ -440,8 +490,8 @@ def TestRun(flags, clang_bin_dir, clang_plugins_dir): ...@@ -440,8 +490,8 @@ def TestRun(flags, clang_bin_dir, clang_plugins_dir):
log("** Output mismatch from running tests. Please run them manually.") log("** Output mismatch from running tests. Please run them manually.")
for line in difflib.context_diff( for line in difflib.context_diff(
expectations.split("\n"), expectations.splitlines(),
output.split("\n"), output.splitlines(),
fromfile=filename, fromfile=filename,
tofile="output", tofile="output",
lineterm="", lineterm="",
......
tools/gcmole/gcmole-test.cc:30:10: warning: Possibly dead variable. tools/gcmole/gcmole-test.cc:30:10: warning: Possibly dead variable.
return obj; return obj;
^ ^
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment