[llvm-cov] Multi-threaded implementation of prepareFileReports method.

Summary:
Local testing has demonstrated a great speed improvement, compare the following:

1) Existing version:
```
$ time llvm-cov show -format=html -output-dir=report -instr-profile=... ...
The tool has been launched:                            00:00:00
Loading coverage data:                                 00:00:00
Get unique source files:                               00:00:33
Creating an index out of the source files:             00:00:34
Going into prepareFileReports:                         00:00:34
Going to emit summary information for each file:       00:28:55 <-- 28:21 min!
Going to emit links to files with no function:         00:28:55
Launching 32 threads for generating HTML files:        00:28:55

real  37m43.651s
user  112m5.540s
sys   7m39.872s
```

2) Multi-threaded version with 32 CPUs:
```
$ time llvm-cov show -format=html -output-dir=report -instr-profile=... ...
The tool has been launched:                            00:00:00
Loading coverage data:                                 00:00:00
Get unique source files:                               00:00:38
Creating an index out of the source files:             00:00:40
Going into prepareFileReports:                         00:00:40
Preparing file reports using 32 threads:               00:00:40
# Creating thread tasks for the following number of files: 16422
Going to emit summary information for each file:       00:01:57 <-- 1:17 min!
Going to emit links to files with no function:         00:01:58
Launching 32 threads for generating HTML files:        00:01:58

real  11m2.044s
user  134m48.124s
sys   7m53.388s
```

Reviewers: vsk, morehouse

Reviewed By: vsk

Subscribers: Dor1s, llvm-commits, kcc

Differential Revision: https://reviews.llvm.org/D41206

llvm-svn: 321871
This commit is contained in:
Max Moroz 2018-01-05 16:15:07 +00:00
parent e565ebcdad
commit cc254ba4a7
15 changed files with 253 additions and 35 deletions

View File

@ -0,0 +1,7 @@
template<typename T>
T abs(T x) {
if (x < 0) {
return -x;
}
return x;
}

View File

@ -0,0 +1,15 @@
#include "abs.h"
#include "bytes.h"
#include "pow.h"
bool loopBytes() {
uint64_t totalInt = 0;
double totalFloat = 0;
for (uint8_t i = 1; i != 0; ++i) {
double a = logarithm(i);
a = abs(a);
totalInt += abs(pow(i, static_cast<uint8_t>(a)));
totalFloat += pow(static_cast<decltype(a)>(i), a);
}
return totalInt > totalFloat;
}

View File

@ -0,0 +1,8 @@
#include <cmath>
#include <cstdint>
inline double logarithm(uint8_t v) {
return log(v);
}
bool loopBytes();

View File

@ -0,0 +1,15 @@
#include "bytes.h"
#include "words.h"
int main() {
bool result = false;
if (loopBytes())
result |= true;
if (loopWords())
result |= true;
if (result)
return 0;
return result;
}

View File

@ -0,0 +1,11 @@
template<typename T>
T pow(T b, T p) {
if (!p)
return 1;
while (--p) {
b *= b;
}
return b;
}

View File

@ -0,0 +1,15 @@
#include "abs.h"
#include "bytes.h"
#include "pow.h"
bool loopWords() {
uint64_t totalInt = 0;
double totalFloat = 0;
for (uint16_t i = 1; i != 0; ++i) {
double a = logarithm(i);
a = abs(a);
totalInt += abs(pow(i, static_cast<uint16_t>(a)));
totalFloat += pow(static_cast<decltype(a)>(i), a);
}
return totalInt > totalFloat;
}

View File

@ -0,0 +1,8 @@
#include <cmath>
#include <cstdint>
inline double logarithm(uint16_t v) {
return log(v);
}
bool loopWords();

View File

@ -0,0 +1,93 @@
# Test "report" command with and without multiple threads.
RUN: llvm-cov report -num-threads=1 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.report
RUN: llvm-cov report -num-threads=10 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.report
RUN: diff %t.1.report %t.2.report
# Test "export" command with and without multiple threads.
RUN: llvm-cov export -num-threads=1 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.json
RUN: llvm-cov export -num-threads=10 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.json
RUN: diff %t.1.json %t.2.json
# Test "show" command with and without multiple threads, single text file.
RUN: llvm-cov show -format=text -num-threads=1 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.text
RUN: llvm-cov show -format=text -num-threads=10 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.text
RUN: diff %t.1.text %t.2.text
# Test "show" command with and without multiple threads, single HTML file.
RUN: llvm-cov show -format=html -num-threads=1 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.html
RUN: llvm-cov show -format=html -num-threads=10 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.html
RUN: diff %t.1.html %t.2.html
# Test "show" command with and without multiple threads, text directory.
RUN: llvm-cov show -format=text -num-threads=1 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.1.text_dir
RUN: llvm-cov show -format=text -num-threads=10 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.2.text_dir
RUN: diff -r %t.1.text_dir %t.2.text_dir
# Test "show" command with and without multiple threads, HTML directory.
RUN: llvm-cov show -format=html -num-threads=1 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.1.html_dir
RUN: llvm-cov show -format=html -num-threads=10 \
RUN: -path-equivalence=/tmp,%S/Inputs \
RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \
RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.2.html_dir
RUN: diff -r %t.1.html_dir %t.2.html_dir
Instructions for regenerating the test:
# cd %S/Inputs/multithreaded_report
cp -r . /tmp/multithreaded_report
clang++ -std=c++11 -mllvm -enable-name-compression=false \
-fprofile-instr-generate -fcoverage-mapping \
/tmp/multithreaded_report/*.cc -o main
LLVM_PROFILE_FILE="main.profraw" ./main
llvm-profdata merge main.profraw -o main.profdata
llvm-cov convert-for-testing ./main -o ./main.covmapping
rm main main.profraw

View File

@ -33,8 +33,8 @@
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/ThreadPool.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/ToolOutputFile.h"
#include <functional>
@ -637,6 +637,12 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
"summary-only", cl::Optional,
cl::desc("Export only summary information for each source file"));
cl::opt<unsigned> NumThreads(
"num-threads", cl::init(0),
cl::desc("Number of merge threads to use (default: autodetect)"));
cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"),
cl::aliasopt(NumThreads));
auto commandLineParser = [&, this](int argc, const char **argv) -> int {
cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
ViewOpts.Debug = DebugDump;
@ -750,6 +756,7 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
ViewOpts.ShowRegionSummary = RegionSummary;
ViewOpts.ShowInstantiationSummary = InstantiationSummary;
ViewOpts.ExportSummaryOnly = SummaryOnly;
ViewOpts.NumThreads = NumThreads;
return 0;
};
@ -809,12 +816,6 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
"project-title", cl::Optional,
cl::desc("Set project title for the coverage report"));
cl::opt<unsigned> NumThreads(
"num-threads", cl::init(0),
cl::desc("Number of merge threads to use (default: autodetect)"));
cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"),
cl::aliasopt(NumThreads));
auto Err = commandLineParser(argc, argv);
if (Err)
return Err;
@ -911,6 +912,8 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
(SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() ||
(ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML);
auto NumThreads = ViewOpts.NumThreads;
// If NumThreads is not specified, auto-detect a good default.
if (NumThreads == 0)
NumThreads =

View File

@ -16,6 +16,8 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ThreadPool.h"
#include "llvm/Support/Threading.h"
#include <numeric>
using namespace llvm;
@ -319,42 +321,60 @@ void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
}
}
void CoverageReport::prepareSingleFileReport(const StringRef Filename,
const coverage::CoverageMapping *Coverage,
const CoverageViewOptions &Options, const unsigned LCP,
FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
std::vector<FunctionCoverageSummary> InstantiationSummaries;
for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
if (!Filters->matches(*Coverage, *F))
continue;
auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
FileReport->addInstantiation(InstantiationSummary);
InstantiationSummaries.push_back(InstantiationSummary);
}
if (InstantiationSummaries.empty())
continue;
auto GroupSummary =
FunctionCoverageSummary::get(Group, InstantiationSummaries);
if (Options.Debug)
outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
<< "size = " << Group.size() << "\n";
FileReport->addFunction(GroupSummary);
}
}
std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
ArrayRef<std::string> Files, const CoverageViewOptions &Options,
const CoverageFilter &Filters) {
std::vector<FileCoverageSummary> FileReports;
unsigned LCP = getRedundantPrefixLen(Files);
auto NumThreads = Options.NumThreads;
// If NumThreads is not specified, auto-detect a good default.
if (NumThreads == 0)
NumThreads =
std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
unsigned(Files.size())));
ThreadPool Pool(NumThreads);
std::vector<FileCoverageSummary> FileReports;
FileReports.reserve(Files.size());
for (StringRef Filename : Files) {
FileCoverageSummary Summary(Filename.drop_front(LCP));
for (const auto &Group : Coverage.getInstantiationGroups(Filename)) {
std::vector<FunctionCoverageSummary> InstantiationSummaries;
for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
if (!Filters.matches(Coverage, *F))
continue;
auto InstantiationSummary = FunctionCoverageSummary::get(Coverage, *F);
Summary.addInstantiation(InstantiationSummary);
Totals.addInstantiation(InstantiationSummary);
InstantiationSummaries.push_back(InstantiationSummary);
}
if (InstantiationSummaries.empty())
continue;
auto GroupSummary =
FunctionCoverageSummary::get(Group, InstantiationSummaries);
if (Options.Debug)
outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
<< "size = " << Group.size() << "\n";
Summary.addFunction(GroupSummary);
Totals.addFunction(GroupSummary);
}
FileReports.push_back(Summary);
FileReports.emplace_back(Filename.drop_front(LCP));
Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
&Coverage, Options, LCP, &FileReports.back(), &Filters);
}
Pool.wait();
for (const auto &FileReport : FileReports)
Totals += FileReport;
return FileReports;
}

View File

@ -44,6 +44,14 @@ public:
const CoverageViewOptions &Options,
const CoverageFilter &Filters = CoverageFiltersMatchAll());
static void
prepareSingleFileReport(const StringRef Filename,
const coverage::CoverageMapping *Coverage,
const CoverageViewOptions &Options,
const unsigned LCP,
FileCoverageSummary *FileReport,
const CoverageFilter *Filters);
/// Render file reports for every unique file in the coverage mapping.
void renderFileReports(raw_ostream &OS) const;

View File

@ -116,6 +116,12 @@ public:
FunctionCoverageInfo(size_t Executed, size_t NumFunctions)
: Executed(Executed), NumFunctions(NumFunctions) {}
FunctionCoverageInfo &operator+=(const FunctionCoverageInfo &RHS) {
Executed += RHS.Executed;
NumFunctions += RHS.NumFunctions;
return *this;
}
void addFunction(bool Covered) {
if (Covered)
++Executed;
@ -176,6 +182,14 @@ struct FileCoverageSummary {
: Name(Name), RegionCoverage(), LineCoverage(), FunctionCoverage(),
InstantiationCoverage() {}
FileCoverageSummary &operator+=(const FileCoverageSummary &RHS) {
RegionCoverage += RHS.RegionCoverage;
LineCoverage += RHS.LineCoverage;
FunctionCoverage += RHS.FunctionCoverage;
InstantiationCoverage += RHS.InstantiationCoverage;
return *this;
}
void addFunction(const FunctionCoverageSummary &Function) {
RegionCoverage += Function.RegionCoverage;
LineCoverage += Function.LineCoverage;

View File

@ -39,6 +39,7 @@ struct CoverageViewOptions {
uint32_t TabSize;
std::string ProjectTitle;
std::string CreatedTimeStr;
unsigned NumThreads;
/// \brief Change the output's stream color if the colors are enabled.
ColoredRawOstream colored_ostream(raw_ostream &OS,