Complete the tool to analyze the efficiency of default sizes.

This commit is contained in:
Benoit Jacob 2015-03-04 09:30:56 -05:00
parent 168ceb271e
commit 00ea121881
2 changed files with 274 additions and 119 deletions

View File

@ -23,20 +23,63 @@
using namespace std;
const int default_precision = 4;
uint8_t log2_pot(size_t x) {
size_t l = 0;
while (x >>= 1) l++;
return l;
}
uint16_t compact_size_triple(size_t k, size_t m, size_t n)
{
return (log2_pot(k) << 8) | (log2_pot(m) << 4) | log2_pot(n);
}
// just a helper to store a triple of K,M,N sizes for matrix product
struct size_triple_t
{
uint16_t k, m, n;
size_triple_t() : k(0), m(0), n(0) {}
size_triple_t(size_t _k, size_t _m, size_t _n) : k(_k), m(_m), n(_n) {}
size_triple_t(const size_triple_t& o) : k(o.k), m(o.m), n(o.n) {}
size_triple_t(uint16_t compact)
{
k = 1 << ((compact & 0xf00) >> 8);
m = 1 << ((compact & 0x0f0) >> 4);
n = 1 << ((compact & 0x00f) >> 0);
}
bool is_cubic() const { return k == m && m == n; }
};
ostream& operator<<(ostream& s, const size_triple_t& t)
{
return s << "(" << t.k << ", " << t.m << ", " << t.n << ")";
}
struct inputfile_entry_t
{
uint16_t product_size;
uint16_t block_size;
uint16_t pot_block_size;
size_triple_t nonpot_block_size;
float gflops;
};
struct inputfile_t
{
enum class type_t {
unknown,
all_pot_sizes,
default_sizes
};
string filename;
vector<inputfile_entry_t> entries;
type_t type;
inputfile_t(const string& fname)
: filename(fname)
, type(type_t::unknown)
{
ifstream stream(filename);
if (!stream.is_open()) {
@ -44,52 +87,96 @@ struct inputfile_t
exit(1);
}
string line;
bool is_in_measurements = false;
while (getline(stream, line)) {
if (line.empty()) continue;
if (line.find("BEGIN MEASUREMENTS") == 0) {
is_in_measurements = true;
if (line.find("BEGIN MEASUREMENTS ALL POT SIZES") == 0) {
if (type != type_t::unknown) {
cerr << "Input file " << filename << " contains redundant BEGIN MEASUREMENTS lines";
exit(1);
}
type = type_t::all_pot_sizes;
continue;
}
if (!is_in_measurements) {
if (line.find("BEGIN MEASUREMENTS DEFAULT SIZES") == 0) {
if (type != type_t::unknown) {
cerr << "Input file " << filename << " contains redundant BEGIN MEASUREMENTS lines";
exit(1);
}
type = type_t::default_sizes;
continue;
}
unsigned int product_size, block_size;
float gflops;
int sscanf_result =
sscanf(line.c_str(), "%x %x %f",
&product_size,
&block_size,
&gflops);
if (3 != sscanf_result ||
!product_size ||
product_size > 0xfff ||
!block_size ||
block_size > 0xfff ||
!isfinite(gflops))
{
cerr << "ill-formed input file: " << filename << endl;
cerr << "offending line:" << endl << line << endl;
exit(1);
if (type == type_t::unknown) {
continue;
}
switch(type) {
case type_t::all_pot_sizes: {
unsigned int product_size, block_size;
float gflops;
int sscanf_result =
sscanf(line.c_str(), "%x %x %f",
&product_size,
&block_size,
&gflops);
if (3 != sscanf_result ||
!product_size ||
product_size > 0xfff ||
!block_size ||
block_size > 0xfff ||
!isfinite(gflops))
{
cerr << "ill-formed input file: " << filename << endl;
cerr << "offending line:" << endl << line << endl;
exit(1);
}
inputfile_entry_t entry;
entry.product_size = uint16_t(product_size);
entry.pot_block_size = uint16_t(block_size);
entry.gflops = gflops;
entries.push_back(entry);
break;
}
case type_t::default_sizes: {
unsigned int product_size;
float gflops;
int bk, bm, bn;
int sscanf_result =
sscanf(line.c_str(), "%x default(%d, %d, %d) %f",
&product_size,
&bk, &bm, &bn,
&gflops);
if (5 != sscanf_result ||
!product_size ||
product_size > 0xfff ||
!isfinite(gflops))
{
cerr << "ill-formed input file: " << filename << endl;
cerr << "offending line:" << endl << line << endl;
exit(1);
}
inputfile_entry_t entry;
entry.product_size = uint16_t(product_size);
entry.pot_block_size = 0;
entry.nonpot_block_size = size_triple_t(bk, bm, bn);
entry.gflops = gflops;
entries.push_back(entry);
break;
}
default:
break;
}
inputfile_entry_t entry;
entry.product_size = uint16_t(product_size);
entry.block_size = uint16_t(block_size);
entry.gflops = gflops;
entries.push_back(entry);
}
stream.close();
if (!is_in_measurements) {
cerr << "Input file " << filename << " didn't contain a BEGIN MEASUREMENTS line. Wrong file?" << endl;
if (type == type_t::unknown) {
cerr << "Unrecognized input file " << filename << endl;
exit(1);
}
if (entries.empty()) {
cerr << "didn't find any measurements in input file: " << filename << endl;
exit(1);
}
//cerr << "read " << entries.size() << " measurements from " << filename << endl;
}
};
@ -114,6 +201,9 @@ struct preprocessed_inputfile_t
preprocessed_inputfile_t(const inputfile_t& inputfile)
: filename(inputfile.filename)
{
if (inputfile.type != inputfile_t::type_t::all_pot_sizes) {
abort();
}
auto it = inputfile.entries.begin();
auto it_first_with_given_product_size = it;
while (it != inputfile.entries.end()) {
@ -145,7 +235,7 @@ private:
for (auto it = begin; it != end; ++it) {
preprocessed_inputfile_entry_t entry;
entry.product_size = it->product_size;
entry.block_size = it->block_size;
entry.block_size = it->pot_block_size;
entry.efficiency = it->gflops / max_gflops;
entries.push_back(entry);
}
@ -415,15 +505,44 @@ void print_partition(
struct action_t
{
virtual const char* invokation_name() const { abort(); return nullptr; }
virtual void run(const vector<preprocessed_inputfile_t>& preprocessed_inputfiles) const { abort(); }
virtual void run(int, char*[]) const { abort(); }
virtual ~action_t() {}
};
struct partition_action_t : action_t
{
virtual const char* invokation_name() const { return "partition"; }
virtual void run(const vector<preprocessed_inputfile_t>& preprocessed_inputfiles) const
virtual void run(int argc, char *argv[]) const
{
vector<preprocessed_inputfile_t> preprocessed_inputfiles;
if (!argc) {
cerr << "The " << invokation_name() << " action needs a list of input files." << endl;
exit(1);
}
vector<string> inputfilenames;
for (int i = 0; i < argc; i++) {
inputfilenames.emplace_back(argv[i]);
}
for (auto it = inputfilenames.begin(); it != inputfilenames.end(); ++it) {
inputfile_t inputfile(*it);
switch (inputfile.type) {
case inputfile_t::type_t::all_pot_sizes:
preprocessed_inputfiles.emplace_back(inputfile);
break;
case inputfile_t::type_t::default_sizes:
cerr << "The " << invokation_name() << " action only uses measurements for all pot sizes, and "
<< "has no use for " << *it << " which contains measurements for default sizes." << endl;
exit(1);
break;
default:
cerr << "Unrecognized input file: " << *it << endl;
exit(1);
}
}
check_all_files_in_same_exact_order(preprocessed_inputfiles);
float required_efficiency_to_beat = 0.0f;
@ -467,89 +586,132 @@ struct partition_action_t : action_t
}
};
uint8_t log2_pot(size_t x) {
size_t l = 0;
while (x >>= 1) l++;
return l;
}
uint16_t compact_size_triple(size_t k, size_t m, size_t n)
{
return (log2_pot(k) << 8) | (log2_pot(m) << 4) | log2_pot(n);
}
// just a helper to store a triple of K,M,N sizes for matrix product
struct size_triple_t
{
size_t k, m, n;
size_triple_t() : k(0), m(0), n(0) {}
size_triple_t(size_t _k, size_t _m, size_t _n) : k(_k), m(_m), n(_n) {}
size_triple_t(const size_triple_t& o) : k(o.k), m(o.m), n(o.n) {}
size_triple_t(uint16_t compact)
{
k = 1 << ((compact & 0xf00) >> 8);
m = 1 << ((compact & 0x0f0) >> 4);
n = 1 << ((compact & 0x00f) >> 0);
}
bool is_cubic() const { return k == m && m == n; }
};
struct evaluate_defaults_action_t : action_t
{
virtual const char* invokation_name() const { return "evaluate-defaults"; }
virtual void run(const vector<preprocessed_inputfile_t>& preprocessed_inputfiles) const
struct results_entry_t {
uint16_t product_size;
size_triple_t default_block_size;
uint16_t best_pot_block_size;
float default_gflops;
float best_pot_gflops;
float default_efficiency;
};
friend ostream& operator<<(ostream& s, const results_entry_t& entry)
{
if (preprocessed_inputfiles.size() > 1) {
cerr << invokation_name() << " only works with one input file." << endl;
exit(1);
return s
<< "Product size " << size_triple_t(entry.product_size)
<< ": default block size " << entry.default_block_size
<< " -> " << entry.default_gflops
<< " GFlop/s = " << entry.default_efficiency * 100.0f << " %"
<< " of best POT block size " << size_triple_t(entry.best_pot_block_size)
<< " -> " << entry.best_pot_gflops
<< " GFlop/s" << dec;
}
static bool lower_efficiency(const results_entry_t& e1, const results_entry_t& e2) {
return e1.default_efficiency < e2.default_efficiency;
}
virtual const char* invokation_name() const { return "evaluate-defaults"; }
void show_usage_and_exit() const
{
cerr << "usage: " << invokation_name() << " default-sizes-data all-pot-sizes-data" << endl;
cerr << "checks how well the performance with default sizes compares to the best "
<< "performance measured over all POT sizes." << endl;
exit(1);
}
virtual void run(int argc, char *argv[]) const
{
if (argc != 2) {
show_usage_and_exit();
}
const preprocessed_inputfile_t& preprocessed_inputfile = preprocessed_inputfiles.front();
inputfile_t inputfile_default_sizes(argv[0]);
inputfile_t inputfile_all_pot_sizes(argv[1]);
if (inputfile_default_sizes.type != inputfile_t::type_t::default_sizes) {
cerr << inputfile_default_sizes.filename << " is not an input file with default sizes." << endl;
show_usage_and_exit();
}
if (inputfile_all_pot_sizes.type != inputfile_t::type_t::all_pot_sizes) {
cerr << inputfile_all_pot_sizes.filename << " is not an input file with all POT sizes." << endl;
show_usage_and_exit();
}
vector<results_entry_t> results;
vector<results_entry_t> cubic_results;
uint16_t product_size = 0;
uint16_t default_block_size = 0;
vector<preprocessed_inputfile_entry_t> results, cubic_results;
for (auto it = preprocessed_inputfile.entries.begin(); it != preprocessed_inputfile.entries.end(); ++it) {
if (it->product_size != product_size) {
product_size = it->product_size;
size_triple_t product_size_triple(product_size);
Eigen::Index k = product_size_triple.k,
m = product_size_triple.m,
n = product_size_triple.n;
Eigen::internal::computeProductBlockingSizes<float, float>(k, m, n);
default_block_size = compact_size_triple(k, m, n);
auto it_all_pot_sizes = inputfile_all_pot_sizes.entries.begin();
for (auto it_default_sizes = inputfile_default_sizes.entries.begin();
it_default_sizes != inputfile_default_sizes.entries.end();
++it_default_sizes)
{
if (it_default_sizes->product_size == product_size) {
continue;
}
if (it->block_size == default_block_size) {
results.push_back(*it);
if (size_triple_t(product_size).is_cubic()) {
cubic_results.push_back(*it);
product_size = it_default_sizes->product_size;
while (it_all_pot_sizes != inputfile_all_pot_sizes.entries.end() &&
it_all_pot_sizes->product_size != product_size)
{
++it_all_pot_sizes;
}
if (it_all_pot_sizes == inputfile_all_pot_sizes.entries.end()) {
break;
}
uint16_t best_pot_block_size = 0;
float best_pot_gflops = 0;
for (auto it = it_all_pot_sizes;
it != inputfile_all_pot_sizes.entries.end() && it->product_size == product_size;
++it)
{
if (it->gflops > best_pot_gflops) {
best_pot_gflops = it->gflops;
best_pot_block_size = it->pot_block_size;
}
}
results_entry_t entry;
entry.product_size = product_size;
entry.default_block_size = it_default_sizes->nonpot_block_size;
entry.best_pot_block_size = best_pot_block_size;
entry.default_gflops = it_default_sizes->gflops;
entry.best_pot_gflops = best_pot_gflops;
entry.default_efficiency = entry.default_gflops / entry.best_pot_gflops;
results.push_back(entry);
size_triple_t t(product_size);
if (t.k == t.m && t.m == t.n) {
cubic_results.push_back(entry);
}
}
cerr << "Below are all results - first column is product size tripe, " << endl
<< "second column is block size triple, in hex kmn form where" << endl
<< "k, m, n are the log2 of the actual values, i.e. a89 means" << endl
<< "k=1024, m=256, n=512." << endl << endl;
cerr << "All results:" << endl;
for (auto it = results.begin(); it != results.end(); ++it) {
cerr << hex << it->product_size << " " << it->block_size
<< " efficiency " << std::dec << 100.0f * it->efficiency << " %" << endl;
cerr << *it << endl;
}
cerr << endl;
sort(results.begin(), results.end(), lower_efficiency);
sort(cubic_results.begin(), cubic_results.end(), lower_efficiency);
cerr << "Efficiency summary: min = "
<< 100.0f * results.front().efficiency << " %, max = "
<< 100.0f * results.back().efficiency << " %, median = "
<< 100.0f * results[results.size() / 2].efficiency << " %" << endl;
cerr << "20% of product sizes have efficiency <= " << 100.0f * results[results.size() * 20 / 100].efficiency << " %" << endl;
cerr << "10% of product sizes have efficiency <= " << 100.0f * results[results.size() * 10 / 100].efficiency << " %" << endl;
cerr << "5% of product sizes have efficiency <= " << 100.0f * results[results.size() * 5 / 100].efficiency << " %" << endl;
cerr << "Cubic sizes efficiency summary: min = "
<< 100.0f * cubic_results.front().efficiency << " %, max = "
<< 100.0f * cubic_results.back().efficiency << " %, median = "
<< 100.0f * cubic_results[cubic_results.size() / 2].efficiency << " %" << endl;
const size_t n = min<size_t>(20, results.size());
cerr << n << " worst results:" << endl;
for (size_t i = 0; i < n; i++) {
cerr << results[i] << endl;
}
cerr << endl;
cerr << "cubic results:" << endl;
for (auto it = cubic_results.begin(); it != cubic_results.end(); ++it) {
cerr << *it << endl;
}
cerr << endl;
sort(cubic_results.begin(), cubic_results.end(), lower_efficiency);
cerr.precision(2);
vector<float> a = {0.5f, 0.20f, 0.10f, 0.05f, 0.02f, 0.01f};
for (auto it = a.begin(); it != a.end(); ++it) {
size_t n = min(results.size() - 1, size_t(*it * results.size()));
cerr << (100.0f * n / (results.size() - 1))
<< " % of product sizes have default efficiency <= "
<< 100.0f * results[n].default_efficiency << " %" << endl;
}
cerr.precision(default_precision);
}
};
@ -568,8 +730,8 @@ void show_usage_and_exit(int argc, char* argv[],
int main(int argc, char* argv[])
{
cout.precision(4);
cerr.precision(4);
cout.precision(default_precision);
cerr.precision(default_precision);
vector<unique_ptr<action_t>> available_actions;
available_actions.emplace_back(new partition_action_t);
@ -577,7 +739,7 @@ int main(int argc, char* argv[])
auto action = available_actions.end();
if (argc <= 2) {
if (argc < 2) {
show_usage_and_exit(argc, argv, available_actions);
}
for (auto it = available_actions.begin(); it != available_actions.end(); ++it) {
@ -591,15 +753,5 @@ int main(int argc, char* argv[])
show_usage_and_exit(argc, argv, available_actions);
}
vector<string> inputfilenames;
for (int i = 2; i < argc; i++) {
inputfilenames.emplace_back(argv[i]);
}
vector<preprocessed_inputfile_t> preprocessed_inputfiles;
for (auto it = inputfilenames.begin(); it != inputfilenames.end(); ++it) {
preprocessed_inputfiles.emplace_back(inputfile_t(*it));
}
(*action)->run(preprocessed_inputfiles);
(*action)->run(argc - 2, argv + 2);
}

View File

@ -122,7 +122,10 @@ ostream& operator<<(ostream& s, const benchmark_t& b)
{
s << hex << b.compact_product_size << dec;
if (b.use_default_block_size) {
s << " default";
size_triple_t t(b.compact_product_size);
Index k = t.k, m = t.m, n = t.n;
internal::computeProductBlockingSizes<MatrixType::Scalar, MatrixType::Scalar>(k, m, n);
s << " default(" << k << ", " << m << ", " << n << ")";
} else {
s << " " << hex << b.compact_block_size << dec;
}
@ -355,7 +358,7 @@ void run_benchmarks(vector<benchmark_t>& benchmarks)
struct measure_all_pot_sizes_action_t : action_t
{
virtual const char* invokation_name() const { return "measure-all-pot-sizes"; }
virtual const char* invokation_name() const { return "all-pot-sizes"; }
virtual void run() const
{
vector<benchmark_t> benchmarks;
@ -386,7 +389,7 @@ struct measure_all_pot_sizes_action_t : action_t
struct measure_default_sizes_action_t : action_t
{
virtual const char* invokation_name() const { return "measure-default-sizes"; }
virtual const char* invokation_name() const { return "default-sizes"; }
virtual void run() const
{
vector<benchmark_t> benchmarks;