[Profiling] Add a -sparse mode to llvm-profdata merge

Add an option to llvm-profdata merge for writing out sparse indexed
profiles. These profiles omit InstrProfRecords for functions which are
never executed.

Differential Revision: http://reviews.llvm.org/D16727

llvm-svn: 259258
This commit is contained in:
Vedant Kumar 2016-01-29 22:54:45 +00:00
parent c5a2db4680
commit e1a3d86600
7 changed files with 125 additions and 44 deletions

View File

@ -90,6 +90,12 @@ OPTIONS
Emit the profile using GCC's gcov format (Not yet supported).
.. option:: -sparse[=true|false]
Do not emit function records with 0 execution count. Can only be used in
conjunction with -instr. Defaults to false, since it can inhibit compiler
optimization during PGO.
EXAMPLES
^^^^^^^^
Basic Usage

View File

@ -32,13 +32,14 @@ public:
typedef SmallDenseMap<uint64_t, InstrProfRecord, 1> ProfilingData;
private:
bool Sparse;
StringMap<ProfilingData> FunctionData;
uint64_t MaxFunctionCount;
// Use raw pointer here for the incomplete type object.
InstrProfRecordWriterTrait *InfoObj;
public:
InstrProfWriter();
InstrProfWriter(bool Sparse = false);
~InstrProfWriter();
/// Add function counts for the given function. If there are already counts
@ -57,8 +58,10 @@ public:
// Internal interface for testing purpose only.
void setValueProfDataEndianness(support::endianness Endianness);
void setOutputSparse(bool Sparse);
private:
bool shouldEncodeData(const ProfilingData &PD);
void writeImpl(ProfOStream &OS);
};

View File

@ -139,8 +139,8 @@ public:
};
}
InstrProfWriter::InstrProfWriter()
: FunctionData(), MaxFunctionCount(0),
InstrProfWriter::InstrProfWriter(bool Sparse)
: Sparse(Sparse), FunctionData(), MaxFunctionCount(0),
InfoObj(new InstrProfRecordWriterTrait()) {}
InstrProfWriter::~InstrProfWriter() { delete InfoObj; }
@ -150,6 +150,9 @@ void InstrProfWriter::setValueProfDataEndianness(
support::endianness Endianness) {
InfoObj->ValueProfDataEndianness = Endianness;
}
void InstrProfWriter::setOutputSparse(bool Sparse) {
this->Sparse = Sparse;
}
std::error_code InstrProfWriter::addRecord(InstrProfRecord &&I,
uint64_t Weight) {
@ -184,11 +187,24 @@ std::error_code InstrProfWriter::addRecord(InstrProfRecord &&I,
return Result;
}
bool InstrProfWriter::shouldEncodeData(const ProfilingData &PD) {
if (!Sparse)
return true;
for (const auto &Func : PD) {
const InstrProfRecord &IPR = Func.second;
if (std::any_of(IPR.Counts.begin(), IPR.Counts.end(),
[](uint64_t Count) { return Count > 0; }))
return true;
}
return false;
}
void InstrProfWriter::writeImpl(ProfOStream &OS) {
OnDiskChainedHashTableGenerator<InstrProfRecordWriterTrait> Generator;
// Populate the hash table generator.
for (const auto &I : FunctionData)
Generator.insert(I.getKey(), &I.getValue());
if (shouldEncodeData(I.getValue()))
Generator.insert(I.getKey(), &I.getValue());
// Write the header.
IndexedInstrProf::Header Header;
Header.Magic = IndexedInstrProf::Magic;
@ -279,10 +295,12 @@ void InstrProfWriter::writeRecordInText(const InstrProfRecord &Func,
void InstrProfWriter::writeText(raw_fd_ostream &OS) {
InstrProfSymtab Symtab;
for (const auto &I : FunctionData)
Symtab.addFuncName(I.getKey());
if (shouldEncodeData(I.getValue()))
Symtab.addFuncName(I.getKey());
Symtab.finalizeSymtab();
for (const auto &I : FunctionData)
for (const auto &Func : I.getValue())
writeRecordInText(Func.second, Symtab, OS);
if (shouldEncodeData(I.getValue()))
for (const auto &Func : I.getValue())
writeRecordInText(Func.second, Symtab, OS);
}

View File

@ -1,6 +1,6 @@
# RUN: llvm-profdata merge -sparse=true %s -o %t.profdata
# RUN: llvm-profdata merge %s -o %t.profdata
# RUN: llvm-profdata merge -sparse=false %s -o %t.profdata.dense
# RUN: llvm-profdata show %t.profdata --function function_count_only --counts | FileCheck %s -check-prefix=FUNC_COUNT_ONLY
function_count_only
@ -12,7 +12,8 @@ function_count_only
# FUNC_COUNT_ONLY-NEXT: Function count: 97531
# FUNC_COUNT_ONLY-NEXT: Block counts: []
# RUN: llvm-profdata show %t.profdata --function "name with spaces" --counts | FileCheck %s -check-prefix=SPACES
# RUN: llvm-profdata show %t.profdata.dense --function "name with spaces" --counts | FileCheck %s -check-prefix=SPACES
# RUN: llvm-profdata show %t.profdata --function "name with spaces" --counts | FileCheck %s --check-prefix=SPARSE_SPACES
name with spaces
1024
2
@ -22,6 +23,7 @@ name with spaces
# SPACES-NEXT: Counters: 2
# SPACES-NEXT: Function count: 0
# SPACES-NEXT: Block counts: [0]
# SPARSE_SPACES-NOT: Function count: 0
# RUN: llvm-profdata show %t.profdata --function large_numbers --counts | FileCheck %s -check-prefix=LARGENUM
large_numbers
@ -38,7 +40,7 @@ large_numbers
# LARGENUM-NEXT: Function count: 2305843009213693952
# LARGENUM-NEXT: Block counts: [1152921504606846976, 576460752303423488, 288230376151711744, 144115188075855872, 72057594037927936]
# RUN: llvm-profdata show %t.profdata --function hex_hash | FileCheck %s -check-prefix=HEX-HASH
# RUN: llvm-profdata show %t.profdata.dense --function hex_hash | FileCheck %s -check-prefix=HEX-HASH
hex_hash
0x1234
1
@ -51,19 +53,21 @@ hex_hash
# NOSUCHFUNC: Functions shown: 0
# RUN: llvm-profdata show %t.profdata --function _ | FileCheck %s -check-prefix=SOMEFUNCS
# RUN: llvm-profdata show %t.profdata.dense --function _ | FileCheck %s -check-prefix=SOMEFUNCS_DENSE
# SOMEFUNCS: Counters:
# SOMEFUNCS: function_count_only:
# SOMEFUNCS: large_numbers:
# SOMEFUNCS: Functions shown: 3
# SOMEFUNCS: Functions shown: 2
# SOMEFUNCS_DENSE: Functions shown: 3
# RUN: llvm-profdata show %t.profdata | FileCheck %s -check-prefix=SUMMARY
# RUN: llvm-profdata show %t.profdata.dense | FileCheck %s -check-prefix=SUMMARY
# SUMMARY-NOT: Counters:
# SUMMARY-NOT: Functions shown:
# SUMMARY: Total functions: 4
# SUMMARY: Maximum function count: 2305843009213693952
# SUMMARY: Maximum internal block count: 1152921504606846976
# RUN: llvm-profdata show --detailed-summary %t.profdata | FileCheck %s -check-prefix=DETAILED-SUMMARY
# RUN: llvm-profdata show --detailed-summary %t.profdata.dense | FileCheck %s -check-prefix=DETAILED-SUMMARY
# DETAILED-SUMMARY: Detailed summary:
# DETAILED-SUMMARY: Total number of blocks: 10
# DETAILED-SUMMARY: Total count: 4539628424389557499

View File

@ -107,7 +107,7 @@ typedef SmallVector<WeightedFile, 5> WeightedFileVector;
static void mergeInstrProfile(const WeightedFileVector &Inputs,
StringRef OutputFilename,
ProfileFormat OutputFormat) {
ProfileFormat OutputFormat, bool OutputSparse) {
if (OutputFilename.compare("-") == 0)
exitWithError("Cannot write indexed profdata format to stdout.");
@ -119,7 +119,7 @@ static void mergeInstrProfile(const WeightedFileVector &Inputs,
if (EC)
exitWithErrorCode(EC, OutputFilename);
InstrProfWriter Writer;
InstrProfWriter Writer(OutputSparse);
SmallSet<std::error_code, 4> WriterErrorCodes;
for (const auto &Input : Inputs) {
auto ReaderOrErr = InstrProfReader::create(Input.Filename);
@ -228,6 +228,9 @@ static int merge_main(int argc, const char *argv[]) {
"GCC encoding (only meaningful for -sample)"),
clEnumValEnd));
cl::opt<bool> OutputSparse("sparse", cl::init(false),
cl::desc("Generate a sparse profile (only meaningful for -instr)"));
cl::ParseCommandLineOptions(argc, argv, "LLVM profile data merger\n");
if (InputFilenames.empty() && WeightedInputFilenames.empty())
@ -241,7 +244,8 @@ static int merge_main(int argc, const char *argv[]) {
WeightedInputs.push_back(parseWeightedFile(WeightedFilename));
if (ProfileKind == instr)
mergeInstrProfile(WeightedInputs, OutputFilename, OutputFormat);
mergeInstrProfile(WeightedInputs, OutputFilename, OutputFormat,
OutputSparse);
else
mergeSampleProfile(WeightedInputs, OutputFilename, OutputFormat);

View File

@ -92,6 +92,7 @@ struct CoverageMappingTest : ::testing::Test {
void SetUp() override {
NextFile = 0;
ProfileWriter.setOutputSparse(false);
}
unsigned getFile(StringRef Name) {
@ -154,7 +155,16 @@ struct CoverageMappingTest : ::testing::Test {
}
};
TEST_F(CoverageMappingTest, basic_write_read) {
struct MaybeSparseCoverageMappingTest
: public CoverageMappingTest,
public ::testing::WithParamInterface<bool> {
void SetUp() {
CoverageMappingTest::SetUp();
ProfileWriter.setOutputSparse(GetParam());
}
};
TEST_P(MaybeSparseCoverageMappingTest, basic_write_read) {
addCMR(Counter::getCounter(0), "foo", 1, 1, 1, 1);
addCMR(Counter::getCounter(1), "foo", 2, 1, 2, 2);
addCMR(Counter::getZero(), "foo", 3, 1, 3, 4);
@ -174,7 +184,7 @@ TEST_F(CoverageMappingTest, basic_write_read) {
}
}
TEST_F(CoverageMappingTest, expansion_gets_first_counter) {
TEST_P(MaybeSparseCoverageMappingTest, expansion_gets_first_counter) {
addCMR(Counter::getCounter(1), "foo", 10, 1, 10, 2);
// This starts earlier in "foo", so the expansion should get its counter.
addCMR(Counter::getCounter(2), "foo", 1, 1, 20, 1);
@ -187,7 +197,7 @@ TEST_F(CoverageMappingTest, expansion_gets_first_counter) {
ASSERT_EQ(3U, OutputCMRs[2].LineStart);
}
TEST_F(CoverageMappingTest, basic_coverage_iteration) {
TEST_P(MaybeSparseCoverageMappingTest, basic_coverage_iteration) {
InstrProfRecord Record("func", 0x1234, {30, 20, 10, 0});
ProfileWriter.addRecord(std::move(Record));
readProfCounts();
@ -210,7 +220,7 @@ TEST_F(CoverageMappingTest, basic_coverage_iteration) {
ASSERT_EQ(CoverageSegment(11, 11, false), Segments[6]);
}
TEST_F(CoverageMappingTest, uncovered_function) {
TEST_P(MaybeSparseCoverageMappingTest, uncovered_function) {
readProfCounts();
addCMR(Counter::getZero(), "file1", 1, 2, 3, 4);
@ -223,7 +233,7 @@ TEST_F(CoverageMappingTest, uncovered_function) {
ASSERT_EQ(CoverageSegment(3, 4, false), Segments[1]);
}
TEST_F(CoverageMappingTest, uncovered_function_with_mapping) {
TEST_P(MaybeSparseCoverageMappingTest, uncovered_function_with_mapping) {
readProfCounts();
addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9);
@ -238,7 +248,7 @@ TEST_F(CoverageMappingTest, uncovered_function_with_mapping) {
ASSERT_EQ(CoverageSegment(9, 9, false), Segments[2]);
}
TEST_F(CoverageMappingTest, combine_regions) {
TEST_P(MaybeSparseCoverageMappingTest, combine_regions) {
InstrProfRecord Record("func", 0x1234, {10, 20, 30});
ProfileWriter.addRecord(std::move(Record));
readProfCounts();
@ -257,9 +267,11 @@ TEST_F(CoverageMappingTest, combine_regions) {
ASSERT_EQ(CoverageSegment(9, 9, false), Segments[3]);
}
TEST_F(CoverageMappingTest, dont_combine_expansions) {
InstrProfRecord Record("func", 0x1234, {10, 20});
ProfileWriter.addRecord(std::move(Record));
TEST_P(MaybeSparseCoverageMappingTest, dont_combine_expansions) {
InstrProfRecord Record1("func", 0x1234, {10, 20});
InstrProfRecord Record2("func", 0x1234, {0, 0});
ProfileWriter.addRecord(std::move(Record1));
ProfileWriter.addRecord(std::move(Record2));
readProfCounts();
addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9);
@ -277,8 +289,8 @@ TEST_F(CoverageMappingTest, dont_combine_expansions) {
ASSERT_EQ(CoverageSegment(9, 9, false), Segments[3]);
}
TEST_F(CoverageMappingTest, strip_filename_prefix) {
InstrProfRecord Record("file1:func", 0x1234, {10});
TEST_P(MaybeSparseCoverageMappingTest, strip_filename_prefix) {
InstrProfRecord Record("file1:func", 0x1234, {0});
ProfileWriter.addRecord(std::move(Record));
readProfCounts();
@ -292,4 +304,7 @@ TEST_F(CoverageMappingTest, strip_filename_prefix) {
ASSERT_EQ("func", Names[0]);
}
INSTANTIATE_TEST_CASE_P(MaybeSparse, MaybeSparseCoverageMappingTest,
::testing::Bool());
} // end anonymous namespace

View File

@ -39,6 +39,8 @@ struct InstrProfTest : ::testing::Test {
InstrProfWriter Writer;
std::unique_ptr<IndexedInstrProfReader> Reader;
void SetUp() { Writer.setOutputSparse(false); }
void readProfile(std::unique_ptr<MemoryBuffer> Profile) {
auto ReaderOrErr = IndexedInstrProfReader::create(std::move(Profile));
ASSERT_TRUE(NoError(ReaderOrErr.getError()));
@ -46,13 +48,24 @@ struct InstrProfTest : ::testing::Test {
}
};
TEST_F(InstrProfTest, write_and_read_empty_profile) {
struct SparseInstrProfTest : public InstrProfTest {
void SetUp() { Writer.setOutputSparse(true); }
};
struct MaybeSparseInstrProfTest : public InstrProfTest,
public ::testing::WithParamInterface<bool> {
void SetUp() {
Writer.setOutputSparse(GetParam());
}
};
TEST_P(MaybeSparseInstrProfTest, write_and_read_empty_profile) {
auto Profile = Writer.writeBuffer();
readProfile(std::move(Profile));
ASSERT_TRUE(Reader->begin() == Reader->end());
}
TEST_F(InstrProfTest, write_and_read_one_function) {
TEST_P(MaybeSparseInstrProfTest, write_and_read_one_function) {
InstrProfRecord Record("foo", 0x1234, {1, 2, 3, 4});
Writer.addRecord(std::move(Record));
auto Profile = Writer.writeBuffer();
@ -70,7 +83,7 @@ TEST_F(InstrProfTest, write_and_read_one_function) {
ASSERT_TRUE(++I == E);
}
TEST_F(InstrProfTest, get_instr_prof_record) {
TEST_P(MaybeSparseInstrProfTest, get_instr_prof_record) {
InstrProfRecord Record1("foo", 0x1234, {1, 2});
InstrProfRecord Record2("foo", 0x1235, {3, 4});
Writer.addRecord(std::move(Record1));
@ -97,7 +110,7 @@ TEST_F(InstrProfTest, get_instr_prof_record) {
ASSERT_TRUE(ErrorEquals(instrprof_error::unknown_function, R.getError()));
}
TEST_F(InstrProfTest, get_function_counts) {
TEST_P(MaybeSparseInstrProfTest, get_function_counts) {
InstrProfRecord Record1("foo", 0x1234, {1, 2});
InstrProfRecord Record2("foo", 0x1235, {3, 4});
Writer.addRecord(std::move(Record1));
@ -124,7 +137,7 @@ TEST_F(InstrProfTest, get_function_counts) {
ASSERT_TRUE(ErrorEquals(instrprof_error::unknown_function, EC));
}
TEST_F(InstrProfTest, get_icall_data_read_write) {
TEST_P(MaybeSparseInstrProfTest, get_icall_data_read_write) {
InstrProfRecord Record1("caller", 0x1234, {1, 2});
InstrProfRecord Record2("callee1", 0x1235, {3, 4});
InstrProfRecord Record3("callee2", 0x1235, {3, 4});
@ -171,7 +184,7 @@ TEST_F(InstrProfTest, get_icall_data_read_write) {
ASSERT_EQ(StringRef((const char *)VD[2].Value, 7), StringRef("callee1"));
}
TEST_F(InstrProfTest, get_icall_data_read_write_with_weight) {
TEST_P(MaybeSparseInstrProfTest, get_icall_data_read_write_with_weight) {
InstrProfRecord Record1("caller", 0x1234, {1, 2});
InstrProfRecord Record2("callee1", 0x1235, {3, 4});
InstrProfRecord Record3("callee2", 0x1235, {3, 4});
@ -217,7 +230,7 @@ TEST_F(InstrProfTest, get_icall_data_read_write_with_weight) {
ASSERT_EQ(StringRef((const char *)VD[2].Value, 7), StringRef("callee1"));
}
TEST_F(InstrProfTest, get_icall_data_read_write_big_endian) {
TEST_P(MaybeSparseInstrProfTest, get_icall_data_read_write_big_endian) {
InstrProfRecord Record1("caller", 0x1234, {1, 2});
InstrProfRecord Record2("callee1", 0x1235, {3, 4});
InstrProfRecord Record3("callee2", 0x1235, {3, 4});
@ -269,7 +282,7 @@ TEST_F(InstrProfTest, get_icall_data_read_write_big_endian) {
Writer.setValueProfDataEndianness(support::little);
}
TEST_F(InstrProfTest, get_icall_data_merge1) {
TEST_P(MaybeSparseInstrProfTest, get_icall_data_merge1) {
static const char caller[] = "caller";
static const char callee1[] = "callee1";
static const char callee2[] = "callee2";
@ -384,7 +397,7 @@ TEST_F(InstrProfTest, get_icall_data_merge1) {
ASSERT_EQ(2U, VD_4[2].Count);
}
TEST_F(InstrProfTest, get_icall_data_merge1_saturation) {
TEST_P(MaybeSparseInstrProfTest, get_icall_data_merge1_saturation) {
static const char bar[] = "bar";
const uint64_t Max = std::numeric_limits<uint64_t>::max();
@ -438,7 +451,7 @@ TEST_F(InstrProfTest, get_icall_data_merge1_saturation) {
// This test tests that when there are too many values
// for a given site, the merged results are properly
// truncated.
TEST_F(InstrProfTest, get_icall_data_merge_site_trunc) {
TEST_P(MaybeSparseInstrProfTest, get_icall_data_merge_site_trunc) {
static const char caller[] = "caller";
InstrProfRecord Record11(caller, 0x1234, {1, 2});
@ -508,7 +521,7 @@ static ValueProfNode *ValueProfNodes[5] = {&Site1Values[0], &Site2Values[0],
nullptr};
static uint16_t NumValueSites[IPVK_Last + 1] = {5};
TEST_F(InstrProfTest, runtime_value_prof_data_read_write) {
TEST_P(MaybeSparseInstrProfTest, runtime_value_prof_data_read_write) {
ValueProfRuntimeRecord RTRecord;
initializeValueProfRuntimeRecord(&RTRecord, &NumValueSites[0],
&ValueProfNodes[0]);
@ -578,7 +591,7 @@ TEST_F(InstrProfTest, runtime_value_prof_data_read_write) {
free(VPData);
}
TEST_F(InstrProfTest, get_max_function_count) {
TEST_P(MaybeSparseInstrProfTest, get_max_function_count) {
InstrProfRecord Record1("foo", 0x1234, {1ULL << 31, 2});
InstrProfRecord Record2("bar", 0, {1ULL << 63});
InstrProfRecord Record3("baz", 0x5678, {0, 0, 0, 0});
@ -591,7 +604,7 @@ TEST_F(InstrProfTest, get_max_function_count) {
ASSERT_EQ(1ULL << 63, Reader->getMaximumFunctionCount());
}
TEST_F(InstrProfTest, get_weighted_function_counts) {
TEST_P(MaybeSparseInstrProfTest, get_weighted_function_counts) {
InstrProfRecord Record1("foo", 0x1234, {1, 2});
InstrProfRecord Record2("foo", 0x1235, {3, 4});
Writer.addRecord(std::move(Record1), 3);
@ -611,7 +624,7 @@ TEST_F(InstrProfTest, get_weighted_function_counts) {
ASSERT_EQ(20U, Counts[1]);
}
TEST_F(InstrProfTest, instr_prof_symtab_test) {
TEST_P(MaybeSparseInstrProfTest, instr_prof_symtab_test) {
std::vector<StringRef> FuncNames;
FuncNames.push_back("func1");
FuncNames.push_back("func2");
@ -662,7 +675,7 @@ TEST_F(InstrProfTest, instr_prof_symtab_test) {
ASSERT_EQ(StringRef("bar3"), R);
}
TEST_F(InstrProfTest, instr_prof_symtab_module_test) {
TEST_P(MaybeSparseInstrProfTest, instr_prof_symtab_module_test) {
LLVMContext Ctx;
std::unique_ptr<Module> M = llvm::make_unique<Module>("MyModule.cpp", Ctx);
FunctionType *FTy = FunctionType::get(Type::getVoidTy(Ctx),
@ -697,7 +710,7 @@ TEST_F(InstrProfTest, instr_prof_symtab_module_test) {
}
}
TEST_F(InstrProfTest, instr_prof_symtab_compression_test) {
TEST_P(MaybeSparseInstrProfTest, instr_prof_symtab_compression_test) {
std::vector<std::string> FuncNames1;
std::vector<std::string> FuncNames2;
for (int I = 0; I < 10 * 1024; I++) {
@ -768,4 +781,22 @@ TEST_F(InstrProfTest, instr_prof_symtab_compression_test) {
}
}
TEST_F(SparseInstrProfTest, preserve_no_records) {
InstrProfRecord Record1("foo", 0x1234, {0});
InstrProfRecord Record2("bar", 0x4321, {0, 0});
InstrProfRecord Record3("bar", 0x4321, {0, 0, 0});
Writer.addRecord(std::move(Record1));
Writer.addRecord(std::move(Record2));
Writer.addRecord(std::move(Record3));
auto Profile = Writer.writeBuffer();
readProfile(std::move(Profile));
auto I = Reader->begin(), E = Reader->end();
ASSERT_TRUE(I == E);
}
INSTANTIATE_TEST_CASE_P(MaybeSparse, MaybeSparseInstrProfTest,
::testing::Bool());
} // end anonymous namespace