Commit c78242c6 authored by sascha.witt's avatar sascha.witt
Browse files

Initial commit

parents
*.o
bench
bench_malloc
results/
This diff is collapsed.
LLVM_CONFIG ?= llvm-config
CXXFLAGS ?= -O3 -DNDEBUG -g -march=native
override CXXFLAGS += -std=c++1y -Wall -Wextra -Werror -pedantic
override LDFLAGS += -lpapi
SRCS := $(filter-out %_malloc.cpp,$(wildcard framework/*.cpp))
OBJS := $(patsubst %.cpp,%.o,$(SRCS))
HDRS := $(wildcard framework/*.h) range_search.h
IMPL := $(wildcard *.h)
OBJS_MALLOC := $(subst bench.o,bench_malloc.o,$(OBJS)) \
$(patsubst %.cpp,%.o,$(wildcard framework/*_malloc.cpp)) \
malloc_count/malloc_count.o
.PHONY: all clean
all: bench bench_malloc
clean:
rm -f framework/*.o malloc_count/malloc_count.o bench bench_malloc debug sanitize
malloc_count/malloc_count.o: malloc_count/malloc_count.c malloc_count/malloc_count.h
$(CC) -O2 -Wall -Werror -g -c -o $@ $<
framework/bench.o: $(IMPL)
framework/%.o: framework/%.cpp $(HDRS)
$(CXX) $(CXXFLAGS) -o $@ -c $<
framework/bench_malloc.o: framework/bench.cpp $(HDRS)
$(CXX) $(CXXFLAGS) -DMALLOC_INSTR -o $@ -c $<
bench: $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
bench_malloc: $(OBJS_MALLOC)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -ldl
sanitize: $(SRCS) $(HDRS) $(IMPL)
$(shell $(LLVM_CONFIG) --bindir)/clang++ $(CXXFLAGS) -stdlib=libc++ \
-Wl,-rpath=$(shell $(LLVM_CONFIG) --libdir) -fsanitize=address \
-o $@ $(SRCS) $(LDFLAGS) -lc++abi
ASAN_SYMBOLIZER_PATH=$(shell $(LLVM_CONFIG) --bindir)/llvm-symbolizer ./$@ -e 16
# Algorithm Engineering Programmmieraufgaben -- Range Search
Implementieren und evaluieren Sie effiziente Datenstrukturen für 2D orthogonal range search in C++11/14.
Nutzen sie dafür das vorgegebene Framework.
Das Framework gibt ein Interface vor, das Sie einhalten müssen ([`range_search.h`](range_search.h)).
Eine Beispiel-Implementierung einer naiven Suche finden sie in [`naive.h`](naive.h).
Da es eine Vielzahl verschiedener Möglichkeiten gibt, kann (und soll) die Aufgabe von mehreren Personen bearbeitet werden.
Mögliche Varianten beinhalten:
- Quad trees
- k-d trees
- Range trees
- Range trees w/ Fractional Cascading
- R trees
- R\* trees
- Hilbert R trees
- Wavelet trees
## Implementierung
Ihre Implementierung sollte generisch sein und den Punkt-Typ als Template-Parameter entgegennehmen.
Es ist allerdings ausreichend, wenn Sie nur 2D-Koordinaten unterstützen (nicht alle Datenstrukturen funktionieren für beliebig viele Dimensionen).
Sie können auf die Koordinaten mittels `p[0]` und `p[1]` zugreifen.
Sie müssen das vorgegebene Interface ([`range_search.h`](range_search.h)) implementieren.
Darin vorgegeben sind folgende Methoden:
- [`void assign(const std::vector<Point>&)`](range_search.h#L21)
Setzt die zugrundeliegende Punktmenge.
Hier sollten Sie alle Vorberechnungsschritte ausführen und Ihre Datenstruktur erstellen.
- [`void reportRange(const Point&, const Point&, std::vector<Point>&)`](range_search.h#L24)
Führt eine orthogonal range reporting query aus.
Die Punkte spannen ein Rechteck auf, wobei der erste die untere linke Ecke darstellt und der zweite die rechte obere.
Alle Punkte der Grundmenge, die sich innerhalb dieses Rechtecks befinden, werden dem vector hinzugefügt.
- [`size_t countRange(const Point&, const Point&)`](range_search.h#L27)
Führt eine orthogonal range counting query aus.
Wie oben, allerdings werden die Punkte nur gezählt anstatt ausgegeben, was oft effizienter implementiert werden kann.
Darüber hinaus müssen Sie Ihre Implementierung registrieren.
Tragen Sie dazu in der Datei [`contenders.h`](contenders.h) sowohl die entsprechende `#include`-Anweisung als auch die zu testenden Datenstrukturen ein.
Dazu geben Sie einen Namen und eine Factory-Funktion an, analog zum enthaltenen Beispiel.
Sollte Ihre Datenstruktur verschiedene Konfigurationen unterstützen (wie zum Beispiel Splitting-Strategien), können und sollten Sie mehrere Einträge erstellen.
Hinweis: Das Interface ist bewusst einfach gehalten, um die Komplexität zu verringern und Ihnen möglichst wenig Steine in den Weg zu legen.
Das geht zu Kosten der Flexibilität; es steht Ihnen aber frei, weitere Methoden zu definieren, um z.B. mit beliebigen Input- und Output-Iteratoren zu arbeiten.
Darüber hinaus sind virtuelle Funktionen natürlich mit einem gewissen Performance-Overhead behaftet.
Dieser betrifft aber alle Implementierungen im gleichen Maße und hat daher keine nennenswerten Auswirkungen auf die Vergleichsmessungen.
## Voraussetzungen
Da das Framework Funktionalitäten aus dem C++11-Standard verwendet, muss ein entsprechender Compiler zum Einsatz kommen.
Empfohlen werden der GNU Compiler `g++` ab Version 4.8 oder das C++-Frontend `clang++` des LLVM Compilers ab Version 3.4.
Bei diesen älteren Versionen ist es unter Umständen nötig, den Standard in der [`Makefile`](Makefile) auf `-std=c++11` zu ändern.
Da das Framework einige POSIX-Funktionen verwendet kann es aktuell nicht unter Windows kompiliert werden.
Folgende Abhängigkeiten werden benötigt:
- [libPAPI](http://icl.cs.utk.edu/papi/) zur Messung von Cache- und Branch-Misses
- [malloc\_count](https://github.com/bingmann/malloc_count) zur Messung des Speicherverbrauchs (im Repository bereits enthalten)
- Optional [sqlplot-tools](https://github.com/bingmann/sqlplot-tools) zur Erzeugung von Plots der Messergebnisse
Als Buildsystem kommt GNU make zum Einsatz.
Dieses erzeugt standardmäßig die Binaries `bench` und `bench_malloc`, die Zeit-, Performance-Counter-, sowie Speichermessungen durchführen.
Darüber hinaus können sie mit Hilfe von `sanitize` den [AddressSanitizer](http://clang.llvm.org/docs/AddressSanitizer.html) von Clang verwenden, um mögliche Speicherlecks oder Zugriffsverletzungen zu finden.
## Verwendung
Bei Ausführung ohne Parameter führt `bench` Laufzeit-, Performance-Counter-, und Speichermessungen für eine Reihe von Benchmarks verschiedener Größen aus.
Mit Hilfe von Kommandozeilen-Parametern können Sie dies Einschränken, was besonders bei der Entwicklung hilfreich ist.
Mögliche Parameter können Sie mittels `bench -h` ausgeben lassen.
Benchmark-Ergebnisse werden auf der Konsole ausgegeben und in maschinenlesbarer Form im Verzeichnis `results/` abgespeichert.
Sie können daraus mit Hilfe von [sqlplot-tools](https://github.com/bingmann/sqlplot-tools) einfach Plots erstellen.
Führen Sie dazu `make` im Unterverzeichnis `plots/` aus.
Ein vorkompiliertes Binary ist dort bereits abgelegt; sollte dieses auf Ihrem System nicht funktionieren, müssen Sie sqlplot-tools manuell installieren.
Standardmäßig werden nur eine Auswahl an möglichen Plots erstellt, die entweder Benchmarks oder Datenstrukturen vergleichen lassen.
Weitere Plots sind möglich, indem Sie den entsprechenden Dateinamen angeben; alternativ können Sie mittels `make everything` alle Messungen visualisieren.
In der Ausarbeitung sollten Sie sich natürlich auf einige wenige, interessante Plots konzentrieren.
#ifndef CONTENDERS_H_
#define CONTENDERS_H_
#include "naive.h"
#elif defined ADD_CONTENDERS
experiments.addContender("Naive", []() { return new Naive<Point>; });
#endif
#include <string.h>
#include <unistd.h>
#include "../contenders.h"
#include "../range_search.h"
#include "command_line_options.h"
#include "benchmark.h"
#include "experiments.h"
#include "generator.h"
#include "instrumentation.h"
#ifdef MALLOC_INSTR
#include "instrumentation_malloc.h"
#endif
using namespace framework;
using Point = std::array<double, 2>;
using RangeSearch = range_search::RangeSearch<Point>;
namespace {
struct RandomBenchmark {
std::string name;
Generator::Distribution distribution;
double min, max;
};
std::vector<RandomBenchmark> randomBenchmarks() {
return {
{ "uniform", Generator::Distribution::kUniform, 0.0, 1.0 },
{ "skewed", Generator::Distribution::kSkewed, 0.0, 1.0 },
{ "normal", Generator::Distribution::kGaussian, -3.0, 3.0 },
{ "clustered", Generator::Distribution::kClusters, 0.0, 1.0 },
{ "stacked", Generator::Distribution::kStackedClusters, 0.0, 1.0 }
};
}
std::unique_ptr<Benchmark<RangeSearch>> make_benchmark(const RandomBenchmark& b,
size_t n, size_t q, bool reporting) {
return std::unique_ptr<Benchmark<RangeSearch>>(new RangeSearchQueries<Point>{
Generator::generatePoints(n, b.distribution, n),
Generator::generateRectangles(q, b.min, b.max, q),
reporting, "\tbench=" + b.name
});
}
} // namespace
int main(int argc, char* argv[]) {
const auto options = CommandLineOptions::parse(argc, argv);
if (options.has_invalid_option)
return -1;
Experiments<RangeSearch> experiments("results/");
const auto addBenchmarks = [&experiments, &options](size_t num) {
for (auto& b : randomBenchmarks())
if (options.hasBenchmark(b.name))
experiments.addBenchmark(b.name + " (n=" + std::to_string(num) + ')',
make_benchmark(b, num, options.num_queries, options.reporting_query));
};
if (options.set_size)
addBenchmarks(options.set_size);
else for (size_t num = 32, exp = 5;
exp <= static_cast<size_t>(options.max_exponent); num *= 2, exp++)
addBenchmarks(num);
#ifndef MALLOC_INSTR
if (options.hasInstrumentation("time"))
experiments.addInstrumentation("time",
std::unique_ptr<Instrumentation>(new TimeInstrumentation()));
if (options.hasInstrumentation("papi"))
experiments.addInstrumentation("papi",
std::unique_ptr<Instrumentation>(new DefaultPapiInstrumentation()));
#else
if (options.hasInstrumentation("memory"))
experiments.addInstrumentation("memory",
std::unique_ptr<Instrumentation>(new MemoryInstrumentation()));
#endif
#define ADD_CONTENDERS
using namespace range_search;
#include "../contenders.h"
#undef ADD_CONTENDERS
if (options.compare) {
if (experiments.compare()) {
std::cerr << "\nQuery results mismatch; aborting benchmark" << std::endl;
return -2;
} else {
std::cout << "Query results match; continuing with benchmark" << std::endl;
}
}
experiments.run(options.iterations, options.append_results);
char** args = nullptr;
#ifndef MALLOC_INSTR
if (options.hasInstrumentation("memory")) {
auto alt_options = options;
alt_options.removeInstrumentation("time");
alt_options.removeInstrumentation("papi");
args = alt_options.makeCommandLine("./bench_malloc");
}
#else
if (options.hasInstrumentation("time") || options.hasInstrumentation("papi")) {
auto alt_options = options;
alt_options.removeInstrumentation("memory");
args = alt_options.makeCommandLine("./bench");
}
#endif
if (args) {
execv(args[0], args);
std::cerr << "Error executing " << args[0] << ": " << strerror(errno) << std::endl;
delete[] args[0];
delete[] args;
return -3;
}
return 0;
}
#ifndef FRAMEWORK_BENCHMARK_H_
#define FRAMEWORK_BENCHMARK_H_
#include <algorithm>
#include <iostream>
#include <ostream>
#include "../range_search.h"
namespace framework {
template<class Datastructure>
class Benchmark {
public:
virtual ~Benchmark() = default;
virtual void runPreprocessing(Datastructure&) = 0;
virtual void runQueries(Datastructure&) = 0;
virtual bool compare(Datastructure& lhs, Datastructure& rhs) = 0;
virtual std::ostream& result(std::ostream&) const = 0;
};
template<class Point>
class RangeSearchQueries : public Benchmark<range_search::RangeSearch<Point>> {
public:
RangeSearchQueries(std::vector<Point> dataset,
std::vector<std::pair<Point, Point>> queries,
bool reporting_query = false,
std::string params = "")
: dataset_(std::move(dataset))
, queries_(std::move(queries))
, params_(std::move(params))
, reporting_query_(reporting_query)
{
if (reporting_query_)
result_.reserve(dataset_.size());
}
void runPreprocessing(range_search::RangeSearch<Point>& rs) override {
rs.assign(dataset_);
}
void runQueries(range_search::RangeSearch<Point>& rs) override {
if (reporting_query_)
for (const auto& q : queries_) {
rs.reportRange(q.first, q.second, result_);
result_.clear();
}
else for (const auto& q : queries_)
rs.countRange(q.first, q.second);
}
std::ostream& result(std::ostream& str) const override {
return str << "\tsize=" << dataset_.size()
<< "\tqueries=" << queries_.size()
<< "\treporting=" << reporting_query_
<< params_;
}
bool compare(range_search::RangeSearch<Point>& lhs, range_search::RangeSearch<Point>& rhs) override {
bool mismatch = false;
std::vector<Point> result2;
std::vector<Point> diff;
for (const auto& q : queries_) {
lhs.reportRange(q.first, q.second, result_);
rhs.reportRange(q.first, q.second, result2);
std::sort(result_.begin(), result_.end());
std::sort(result2.begin(), result2.end());
if (result_ != result2) {
mismatch = true;
std::cerr << "Mismatch in query [(" << q.first[0] << ',' << q.first[1]
<< "), (" << q.second[0] << ',' << q.second[1] << ")]";
std::cerr << "\nExpected:";
std::set_difference(result_.begin(), result_.end(), result2.begin(),
result2.end(), std::back_inserter(diff));
printPoints(diff);
std::cerr << "\nGot: ";
std::set_difference(result2.begin(), result2.end(), result_.begin(),
result_.end(), std::back_inserter(diff));
printPoints(diff);
std::cerr << std::endl;
}
result_.clear();
result2.clear();
}
return mismatch;
}
private:
std::vector<Point> dataset_;
std::vector<std::pair<Point, Point>> queries_;
std::string params_;
std::vector<Point> result_;
bool reporting_query_;
static void printPoints(std::vector<Point>& points) {
const size_t size = points.size();
if (size > 10) points.resize(10);
for (const auto& p : points)
std::cerr << " (" << p[0] << ',' << p[1] << ')';
if (size > points.size())
std::cerr << " ... and " << size - points.size() << " more";
points.clear();
}
};
} // namespace framework
#endif // FRAMEWORK_BENCHMARK_H_
#include "command_line_options.h"
#include <getopt.h>
#include <string.h>
#include <iostream>
#include <stdexcept>
#include <type_traits>
namespace framework {
constexpr decltype(CommandLineOptions::benchmarkNames) CommandLineOptions::benchmarkNames;
constexpr decltype(CommandLineOptions::instrumentationNames) CommandLineOptions::instrumentationNames;
namespace {
bool setBitset(const char* arg, int& bs, const char* const* names, int count) {
for (int i = 0; i < count; ++i)
if (!strcmp(names[i], arg)) {
bs |= (1 << i);
return true;
}
return false;
}
bool getBitset(const std::string& name, int bs, const char* const* names, int count) {
if (!bs) return true;
for (int i = 0; i < count; ++i)
if (name == names[i])
return bs & (1 << i);
throw std::domain_error("Invalid option: " + name);
}
} // namespace
void CommandLineOptions::printUsage(const char* program_name) {
std::cerr << "Usage: " << program_name << " <options>..."
"\n -h, --help print this message"
"\n -a, --append append results instead of overwriting"
"\n -b, --benchmark <name> benchmark(s) to run. Default is all."
"\n Valid arguments: uniform, skewed,"
"\n normal, clustered, stacked"
"\n -c, --compare compare query results before benchmarking"
"\n -e, --max-exponent <n> generate benchmarks with up to 2^n points."
"\n Default is 22. See also -n."
"\n -i, --iterations <n> set number of iterations of experiments"
"\n -m, --instrumentation <name> instrumentations to use (default is all)"
"\n valid arguments: time, papi, memory"
"\n -n, --num-points <n> set number of points in generated data sets"
"\n -q, --num-queries <n> set number of queries to run. Default is 10000."
"\n -r, --reporting-query do range reporting query. Default is counting."
<< std::endl;
}
CommandLineOptions CommandLineOptions::parse(int argc, char* const argv[]) {
CommandLineOptions o;
::option options[] = {
{ "help", no_argument, nullptr, 'h' },
{ "append", no_argument, &o.append_results, 1},
{ "benchmark", required_argument, nullptr, 'b' },
{ "compare", no_argument, nullptr, 'c' },
{ "iterations", required_argument, nullptr, 'i' },
{ "instrumentation", required_argument, nullptr, 'm' },
{ "max-exponent", required_argument, nullptr, 'e' },
{ "num-points", required_argument, nullptr, 'n' },
{ "num-queries", required_argument, nullptr, 'q' },
{ "reporting-query", no_argument, &o.reporting_query, 1},
{ 0, 0, 0, 0 }
};
opterr = 0;
int c;
while ((c = getopt_long(argc, argv, ":ab:ce:hi:m:n:q:r", options, nullptr)) != -1) {
switch (c) {
case 0: break;
case 'a':
o.append_results = 1;
break;
case 'b':
if (!setBitset(optarg, o.benchmarks, benchmarkNames,
std::extent<decltype(benchmarkNames)>::value)) {
std::cerr << "Invalid argument to '-b': " << optarg << std::endl;
o.has_invalid_option = true;
}
break;
case 'c':
o.compare = 1;
break;
case 'e':
o.max_exponent = std::atoi(optarg);
if (o.max_exponent <= 4) {
std::cerr << "Invalid argument to '-e': Must be > 4" << std::endl;
o.has_invalid_option = true;
}
break;
case 'h':
o.has_invalid_option = true;
break;
case 'i':
o.iterations = std::atoi(optarg);
if (o.iterations <= 0) {
std::cerr << "Invalid argument to '-i': Must be > 0" << std::endl;
o.has_invalid_option = true;
}
break;
case 'm':
if (!setBitset(optarg, o.instrumentations, instrumentationNames,
std::extent<decltype(instrumentationNames)>::value)) {
std::cerr << "Invalid argument to '-m': " << optarg << std::endl;
o.has_invalid_option = true;
}
break;
case 'n':
o.set_size = std::atol(optarg);
if (o.set_size <= 0) {
std::cerr << "Invalid argument to '-n': Must be > 0" << std::endl;
o.has_invalid_option = true;
}
break;
case 'q':
o.num_queries = std::atoi(optarg);
if (o.num_queries <= 0) {
std::cerr << "Invalid argument to '-q': Must be > 0" << std::endl;
o.has_invalid_option = true;
}
break;
case 'r':
o.reporting_query = 1;
break;
case ':':
o.has_invalid_option = true;
if (optopt)
std::cerr << "Missing argument to '-" << static_cast<char>(optopt) << '\'' << std::endl;
else
std::cerr << "Missing argument to '" << argv[optind - 1] << '\''<< std::endl;
break;
case '?':
default:
o.has_invalid_option = true;
if (optopt)
std::cerr << "Invalid option '-" << static_cast<char>(optopt) << '\''<< std::endl;
else
std::cerr << "Invalid option '" << argv[optind - 1] << '\''<< std::endl;
}
}
if (o.has_invalid_option)
printUsage(argv[0]);
return o;
}
bool CommandLineOptions::hasBenchmark(const std::string& name) const {
return getBitset(name, benchmarks, benchmarkNames,
std::extent<decltype(benchmarkNames)>::value);
}
bool CommandLineOptions::hasInstrumentation(const std::string& name) const {
return getBitset(name, instrumentations, instrumentationNames,
std::extent<decltype(instrumentationNames)>::value);
}
void CommandLineOptions::removeInstrumentation(const std::string& name) {
size_t index = 0;
for (; index < std::extent<decltype(instrumentationNames)>::value; ++index)
if (name == instrumentationNames[index]) break;
if (index >= std::extent<decltype(instrumentationNames)>::value)
throw std::domain_error("Invalid option: " + name);
if (!instrumentations) {
instrumentations = ~(1 << index);
} else {
instrumentations &= ~(1 << index);
}
}
char** CommandLineOptions::makeCommandLine(const char* program_name) const {
char** argv = new char*[32];
argv[0] = new char[1024];
char* p = argv[0];
int argc = 0;
const auto add = [&](const char* opt) {
argv[argc++] = p;
do {
*p++ = *opt;
} while (*opt++);
};
const auto addN = [&](size_t n) {
argv[argc++] = p;
p += sprintf(p, "%tu", n);
};
add(program_name);
if (benchmarks)
for (size_t i = 0; i < std::extent<decltype(benchmarkNames)>::value; ++i)
if (benchmarks & (1 << i)) {
add("-b");
add(benchmarkNames[i]);
}
if (instrumentations)
for (size_t i = 0; i < std::extent<decltype(instrumentationNames)>::value; ++i)
if (instrumentations & (1 << i)) {
add("-m");
add(instrumentationNames[i]);
}
add("-e");
addN(max_exponent);
if (set_size) {
add("-n");
addN(set_size);
}
add("-q");
addN(num_queries);
add("-i");
addN(iterations);
if (reporting_query)
add("-r");
if (append_results)
add("-a");
// never compare, already did that this run if requested
argv[argc] = nullptr;
return argv;
}
} // namespace framework