summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordrd <drd@ableton.com>2017-02-16 17:28:54 +0100
committerdrd <drd@ableton.com>2017-02-16 17:45:25 +0100
commite2f6b686003f86af8a64e7c6f7e8cf8c2da7ba69 (patch)
treea57fe24b425ad37b3ce5cf32aefec99cee7ede12
parente29b2efa89ccfab0c631dae771b8c0e5b9616839 (diff)
downloadingen-e2f6b686003f86af8a64e7c6f7e8cf8c2da7ba69.tar.gz
ingen-e2f6b686003f86af8a64e7c6f7e8cf8c2da7ba69.tar.bz2
ingen-e2f6b686003f86af8a64e7c6f7e8cf8c2da7ba69.zip
Preliminary alternative parallel traversal/benchmarking work
-rw-r--r--ingen/World.hpp14
-rw-r--r--src/World.cpp55
-rw-r--r--src/gui/ingen_gui_lv2.cpp5
-rw-r--r--src/ingen/ingen.cpp3
-rw-r--r--src/server/CompiledGraph.cpp84
-rw-r--r--src/server/CompiledGraph.hpp18
-rw-r--r--src/server/Engine.cpp1
-rw-r--r--src/server/Task.cpp83
-rw-r--r--src/server/Task.hpp49
-rw-r--r--src/server/ingen_lv2.cpp6
-rw-r--r--tests/TestClient.hpp90
-rw-r--r--tests/ingen_bench.cpp189
-rw-r--r--tests/ingen_test.cpp74
-rw-r--r--wscript19
14 files changed, 458 insertions, 232 deletions
diff --git a/ingen/World.hpp b/ingen/World.hpp
index fc77593a..b0e09b34 100644
--- a/ingen/World.hpp
+++ b/ingen/World.hpp
@@ -64,20 +64,20 @@ class URIs;
class INGEN_API World : public Raul::Noncopyable {
public:
/** Construct a new Ingen world.
- * @param argc Argument count (as in C main())
- * @param argv Argument vector (as in C main())
* @param map LV2 URID map implementation, or NULL to use internal.
* @param unmap LV2 URID unmap implementation, or NULL to use internal.
* @param log LV2 log implementation, or NULL to use internal.
*/
- World(int& argc,
- char**& argv,
- LV2_URID_Map* map,
- LV2_URID_Unmap* unmap,
- LV2_Log_Log* log);
+ World(LV2_URID_Map* map, LV2_URID_Unmap* unmap, LV2_Log_Log* log);
virtual ~World();
+ /** Load configuration from files and command line.
+ * @param argc Argument count (as in C main())
+ * @param argv Argument vector (as in C main())
+ */
+ virtual void load_configuration(int& argc, char**& argv);
+
/** Load an Ingen module by name (e.g. "server", "gui", etc.)
* @return True on success.
*/
diff --git a/src/World.cpp b/src/World.cpp
index 38b27a9a..d6a1fc1c 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -98,14 +98,12 @@ ingen_load_module(Log& log, const string& name)
class World::Impl {
public:
- Impl(int& a_argc,
- char**& a_argv,
- LV2_URID_Map* map,
+ Impl(LV2_URID_Map* map,
LV2_URID_Unmap* unmap,
LV2_Log_Log* lv2_log)
- : argc(a_argc)
- , argv(a_argv)
- , lv2_features(NULL)
+ : argc(nullptr)
+ , argv(nullptr)
+ , lv2_features(nullptr)
, rdf_world(new Sord::World())
, lilv_world(lilv_world_new())
, uri_map(new URIMap(log, map, unmap))
@@ -114,17 +112,6 @@ public:
, conf(*forge)
, log(lv2_log, *uris)
{
- // Parse default configuration files
- std::list<std::string> files = conf.load_default("ingen", "options.ttl");
- for (const auto& f : files) {
- log.info(fmt("Loaded configuration %1%\n") % f);
- }
-
- // Parse command line options, overriding configuration file values
- conf.parse(argc, argv);
- log.set_flush(conf.option("flush-log").get<int32_t>());
- log.set_trace(conf.option("trace").get<int32_t>());
-
lv2_features = new LV2Features();
lv2_features->add_feature(uri_map->urid_map_feature());
lv2_features->add_feature(uri_map->urid_unmap_feature());
@@ -208,8 +195,8 @@ public:
typedef std::map<const std::string, ScriptRunner> ScriptRunners;
ScriptRunners script_runners;
- int& argc;
- char**& argv;
+ int* argc;
+ char*** argv;
LV2Features* lv2_features;
Sord::World* rdf_world;
LilvWorld* lilv_world;
@@ -228,12 +215,8 @@ public:
std::string jack_uuid;
};
-World::World(int& argc,
- char**& argv,
- LV2_URID_Map* map,
- LV2_URID_Unmap* unmap,
- LV2_Log_Log* log)
- : _impl(new Impl(argc, argv, map, unmap, log))
+World::World(LV2_URID_Map* map, LV2_URID_Unmap* unmap, LV2_Log_Log* log)
+ : _impl(new Impl(map, unmap, log))
{
_impl->serialiser = SPtr<Serialiser>(new Serialiser(*this));
_impl->parser = SPtr<Parser>(new Parser());
@@ -244,6 +227,24 @@ World::~World()
delete _impl;
}
+void
+World::load_configuration(int& argc, char**& argv)
+{
+ _impl->argc = &argc;
+ _impl->argv = &argv;
+
+ // Parse default configuration files
+ const auto files = _impl->conf.load_default("ingen", "options.ttl");
+ for (const auto& f : files) {
+ _impl->log.info(fmt("Loaded configuration %1%\n") % f);
+ }
+
+ // Parse command line options, overriding configuration file values
+ _impl->conf.parse(argc, argv);
+ _impl->log.set_flush(_impl->conf.option("flush-log").get<int32_t>());
+ _impl->log.set_trace(_impl->conf.option("trace").get<int32_t>());
+}
+
void World::set_engine(SPtr<EngineBase> e) { _impl->engine = e; }
void World::set_interface(SPtr<Interface> i) { _impl->interface = i; }
void World::set_store(SPtr<Store> s) { _impl->store = s; }
@@ -254,8 +255,8 @@ SPtr<Parser> World::parser() { return _impl->parser; }
SPtr<Serialiser> World::serialiser() { return _impl->serialiser; }
SPtr<Store> World::store() { return _impl->store; }
-int& World::argc() { return _impl->argc; }
-char**& World::argv() { return _impl->argv; }
+int& World::argc() { return *_impl->argc; }
+char**& World::argv() { return *_impl->argv; }
Configuration& World::conf() { return _impl->conf; }
Log& World::log() { return _impl->log; }
diff --git a/src/gui/ingen_gui_lv2.cpp b/src/gui/ingen_gui_lv2.cpp
index 1f950089..005ed270 100644
--- a/src/gui/ingen_gui_lv2.cpp
+++ b/src/gui/ingen_gui_lv2.cpp
@@ -110,10 +110,11 @@ instantiate(const LV2UI_Descriptor* descriptor,
}
}
- ui->world = new Ingen::World(ui->argc, ui->argv, map, unmap, log);
-
+ ui->world = new Ingen::World(map, unmap, log);
ui->forge = new Ingen::Forge(ui->world->uri_map());
+ ui->world->load_configuration(ui->argc, ui->argv);
+
if (!ui->world->load_module("client")) {
delete ui;
return NULL;
diff --git a/src/ingen/ingen.cpp b/src/ingen/ingen.cpp
index 9f939621..e4f0a946 100644
--- a/src/ingen/ingen.cpp
+++ b/src/ingen/ingen.cpp
@@ -90,7 +90,8 @@ main(int argc, char** argv)
// Create world
try {
- world = new Ingen::World(argc, argv, NULL, NULL, NULL);
+ world = new Ingen::World(NULL, NULL, NULL);
+ world->load_configuration(argc, argv);
if (argc <= 1) {
world->conf().print_usage("ingen", cout);
return EXIT_FAILURE;
diff --git a/src/server/CompiledGraph.cpp b/src/server/CompiledGraph.cpp
index b808cb90..5553140c 100644
--- a/src/server/CompiledGraph.cpp
+++ b/src/server/CompiledGraph.cpp
@@ -1,6 +1,6 @@
/*
This file is part of Ingen.
- Copyright 2015-2016 David Robillard <http://drobilla.net/>
+ Copyright 2015-2017 David Robillard <http://drobilla.net/>
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
@@ -43,7 +43,7 @@ public:
CompiledGraph::CompiledGraph(GraphImpl* graph)
: _log(graph->engine().log())
, _path(graph->path())
- , _master(Task::Mode::SEQUENTIAL)
+ , _master(new Task(Task::Mode::PARALLEL))
{
compile_graph(graph);
}
@@ -76,7 +76,7 @@ CompiledGraph::compile_set(const std::set<BlockImpl*>& blocks,
// Each block is the start of a new sequential task
Task seq(Task::Mode::SEQUENTIAL);
compile_block(block, seq, k);
- task.push_back(seq);
+ task.push_front(std::move(seq));
}
}
@@ -91,41 +91,29 @@ CompiledGraph::compile_graph(GraphImpl* graph)
// Mark all blocks as unvisited initially
b.set_mark(BlockImpl::Mark::UNVISITED);
- if (b.providers().empty()) {
- // Block has no dependencies, add to initial working set
+ if (b.dependants().empty()) {
+ // Block has no dependants, add to initial working set
blocks.insert(&b);
}
}
- // Compile initial working set into master task
- Task start(Task::Mode::PARALLEL);
- std::set<BlockImpl*> next;
- compile_set(blocks, start, next);
- _master.push_back(start);
-
// Keep compiling working set until all connected nodes are visited
- while (!next.empty()) {
- blocks.clear();
+ while (!blocks.empty()) {
+ std::set<BlockImpl*> next;
+
// The working set is a parallel task...
Task par(Task::Mode::PARALLEL);
- for (BlockImpl* block : next) {
- // ... where each block is the start of a new sequential task
+ for (BlockImpl* block : blocks) {
+ // ... where each block is the tail of a new sequential task
Task seq(Task::Mode::SEQUENTIAL);
- compile_block(block, seq, blocks);
- par.push_back(seq);
- }
- _master.push_back(par);
- next = blocks;
- }
-
- // Compile any nodes that weren't reached (disconnected cycles)
- for (auto& b : graph->blocks()) {
- if (b.get_mark() == BlockImpl::Mark::UNVISITED) {
- compile_block(&b, _master, next);
+ compile_block(block, seq, next);
+ par.push_front(std::move(seq));
}
+ _master->push_front(std::move(par));
+ blocks = next;
}
- _master.simplify();
+ _master = Task::simplify(std::move(_master));
if (graph->engine().world()->conf().option("trace").get<int32_t>()) {
dump([this](const std::string& msg) {
@@ -160,23 +148,23 @@ check_feedback(const BlockImpl* root, BlockImpl* dependant)
}
void
-CompiledGraph::compile_dependant(const BlockImpl* root,
- BlockImpl* block,
- Task& task,
- std::set<BlockImpl*>& k)
+CompiledGraph::compile_provider(const BlockImpl* root,
+ BlockImpl* block,
+ Task& task,
+ std::set<BlockImpl*>& k)
{
- if (block->providers().size() > 1) {
- /* Dependant has other providers, so this is the start of a sequential task.
- Add dependant to future working set and stop traversal. */
+ if (block->dependants().size() > 1) {
+ /* Provider has other dependants, so this is the start of a sequential task.
+ Add provider to future working set and stop traversal. */
check_feedback(root, block);
k.insert(block);
} else {
- // Dependant has only this provider, add here
+ // Provider has only this dependant, add here
if (task.mode() == Task::Mode::PARALLEL) {
// Inside a parallel task, compile into a new sequential child
Task seq(Task::Mode::SEQUENTIAL);
compile_block(block, seq, k);
- task.push_back(seq);
+ task.push_front(std::move(seq));
} else {
// Append to given sequential task
compile_block(block, task, k);
@@ -191,21 +179,21 @@ CompiledGraph::compile_block(BlockImpl* n, Task& task, std::set<BlockImpl*>& k)
case BlockImpl::Mark::UNVISITED:
n->set_mark(BlockImpl::Mark::VISITING);
- // Execute this task before the dependants to follow
- task.push_back(Task(Task::Mode::SINGLE, n));
+ // Execute this task after the providers to follow
+ task.push_front(Task(Task::Mode::SINGLE, n));
- if (n->dependants().size() < 2) {
- // Single dependant, append to this sequential task
- for (auto& d : n->dependants()) {
- compile_dependant(n, d, task, k);
+ if (n->providers().size() < 2) {
+ // Single provider, append to this sequential task
+ for (auto& d : n->providers()) {
+ compile_provider(n, d, task, k);
}
} else {
- // Multiple dependants, create a new parallel task
+ // Multiple providers, create a new parallel task
Task par(Task::Mode::PARALLEL);
- for (auto& d : n->dependants()) {
- compile_dependant(n, d, par, k);
+ for (auto& d : n->providers()) {
+ compile_provider(n, d, par, k);
}
- task.push_back(par);
+ task.push_front(std::move(par));
}
n->set_mark(BlockImpl::Mark::VISITED);
break;
@@ -221,7 +209,7 @@ CompiledGraph::compile_block(BlockImpl* n, Task& task, std::set<BlockImpl*>& k)
void
CompiledGraph::run(RunContext& context)
{
- _master.run(context);
+ _master->run(context);
}
void
@@ -229,7 +217,7 @@ CompiledGraph::dump(std::function<void (const std::string&)> sink) const
{
sink("(compiled-graph ");
sink(_path);
- _master.dump(sink, 2, false);
+ _master->dump(sink, 2, false);
sink(")\n");
}
diff --git a/src/server/CompiledGraph.hpp b/src/server/CompiledGraph.hpp
index eeeb6111..db9aa288 100644
--- a/src/server/CompiledGraph.hpp
+++ b/src/server/CompiledGraph.hpp
@@ -1,6 +1,6 @@
/*
This file is part of Ingen.
- Copyright 2007-2016 David Robillard <http://drobilla.net/>
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
@@ -63,14 +63,14 @@ private:
void compile_graph(GraphImpl* graph);
void compile_set(const BlockSet& blocks, Task& task, BlockSet& k);
void compile_block(BlockImpl* block, Task& task, BlockSet& k);
- void compile_dependant(const BlockImpl* root,
- BlockImpl* block,
- Task& task,
- BlockSet& k);
-
- Log& _log;
- const Raul::Path _path;
- Task _master;
+ void compile_provider(const BlockImpl* root,
+ BlockImpl* block,
+ Task& task,
+ BlockSet& k);
+
+ Log& _log;
+ const Raul::Path _path;
+ std::unique_ptr<Task> _master;
};
} // namespace Server
diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp
index 794088bc..5561c36e 100644
--- a/src/server/Engine.cpp
+++ b/src/server/Engine.cpp
@@ -102,6 +102,7 @@ Engine::Engine(Ingen::World* world)
Raul::RingBuffer* ring = new Raul::RingBuffer(24 * event_queue_size());
_notifications.push_back(ring);
_run_contexts.push_back(new RunContext(*this, ring, i, i > 0));
+ _run_contexts.back()->set_priority(90); // FIXME
}
_world->lv2_features().add_feature(_worker->schedule_feature());
diff --git a/src/server/Task.cpp b/src/server/Task.cpp
index e19855d3..bf1f5028 100644
--- a/src/server/Task.cpp
+++ b/src/server/Task.cpp
@@ -1,6 +1,6 @@
/*
This file is part of Ingen.
- Copyright 2015-2016 David Robillard <http://drobilla.net/>
+ Copyright 2015-2017 David Robillard <http://drobilla.net/>
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
@@ -30,14 +30,14 @@ Task::run(RunContext& context)
_block->process(context);
break;
case Mode::SEQUENTIAL:
- for (Task& task : *this) {
- task.run(context);
+ for (const auto& task : _children) {
+ task->run(context);
}
break;
case Mode::PARALLEL:
// Initialize (not) done state of sub-tasks
- for (Task& task : *this) {
- task.set_done(false);
+ for (const auto& task : _children) {
+ task->set_done(false);
}
// Grab the first sub-task
@@ -65,12 +65,12 @@ Task::steal(RunContext& context)
{
if (_mode == Mode::PARALLEL) {
const unsigned i = _next++;
- if (i < size()) {
- return &(*this)[i];
+ if (i < _children.size()) {
+ return _children[i].get();
}
}
- return NULL;
+ return nullptr;
}
Task*
@@ -82,52 +82,65 @@ Task::get_task(RunContext& context)
return t;
}
+ int num_spins = 0;
while (true) {
// Push done end index as forward as possible
- for (; _done_end < size() && (*this)[_done_end].done(); ++_done_end) {}
+ while (_done_end < _children.size() && _children[_done_end]->done()) {
+ ++_done_end;
+ }
- if (_done_end >= size()) {
- return NULL; // All child tasks are finished
+ if (_done_end >= _children.size()) {
+ return nullptr; // All child tasks are finished
}
// All child tasks claimed, but some are unfinished, steal a task
- t = context.engine().steal_task(context.id() + 1);
- if (t) {
+ if ((t = context.engine().steal_task(context.id() + 1))) {
return t;
}
+ ++num_spins;
/* All child tasks are claimed, and we failed to steal any tasks. Spin
to prevent blocking, though it would probably be wiser to wait for a
signal in non-main threads, and maybe even in the main thread
depending on your real-time safe philosophy... more experimentation
here is needed. */
}
+
+ if (num_spins > 0) {
+ fprintf(stderr, "Num spins: %u\n", num_spins);
+ }
}
-void
-Task::simplify()
+std::unique_ptr<Task>
+Task::simplify(std::unique_ptr<Task>&& task)
{
- if (_mode != Mode::SINGLE) {
- for (std::vector<Task>::iterator t = begin(); t != end();) {
- t->simplify();
- if (t->mode() != Mode::SINGLE && t->empty()) {
- // Empty task, erase
- t = erase(t);
- } else if (t->mode() == _mode) {
- // Subtask with the same type, fold child into parent
- const Task child(*t);
- t = erase(t);
- t = insert(t, child.begin(), child.end());
- } else {
- ++t;
- }
- }
+ if (task->mode() == Mode::SINGLE) {
+ return std::move(task);
+ }
- if (size() == 1) {
- const Task t(front());
- *this = t;
+ for (auto t = task->_children.begin(); t != task->_children.end();) {
+ *t = simplify(std::move(*t));
+ if ((*t)->empty()) {
+ // Empty task, erase
+ t = task->_children.erase(t);
+ } else if ((*t)->mode() == task->_mode) {
+ // Subtask with the same type, fold child into parent
+ std::unique_ptr<Task> child(std::move(*t));
+ t = task->_children.erase(t);
+ t = task->_children.insert(
+ t,
+ std::make_move_iterator(child->_children.begin()),
+ std::make_move_iterator(child->_children.end()));
+ } else {
+ ++t;
}
}
+
+ if (task->_children.size() == 1) {
+ return std::move(task->_children.front());
+ }
+
+ return std::move(task);
}
void
@@ -144,8 +157,8 @@ Task::dump(std::function<void (const std::string&)> sink, unsigned indent, bool
sink(_block->path());
} else {
sink(((_mode == Mode::SEQUENTIAL) ? "(seq " : "(par "));
- for (size_t i = 0; i < size(); ++i) {
- (*this)[i].dump(sink, indent + 5, i == 0);
+ for (size_t i = 0; i < _children.size(); ++i) {
+ _children[i]->dump(sink, indent + 5, i == 0);
}
sink(")");
}
diff --git a/src/server/Task.hpp b/src/server/Task.hpp
index 2c1a0cc2..982a6206 100644
--- a/src/server/Task.hpp
+++ b/src/server/Task.hpp
@@ -1,6 +1,6 @@
/*
This file is part of Ingen.
- Copyright 2007-2016 David Robillard <http://drobilla.net/>
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
@@ -18,8 +18,9 @@
#define INGEN_ENGINE_TASK_HPP
#include <cassert>
+#include <deque>
+#include <memory>
#include <ostream>
-#include <vector>
namespace Ingen {
namespace Server {
@@ -27,7 +28,7 @@ namespace Server {
class BlockImpl;
class RunContext;
-class Task : public std::vector<Task> {
+class Task {
public:
enum class Mode {
SINGLE, ///< Single block to run
@@ -35,7 +36,7 @@ public:
PARALLEL ///< Elements may be run in any order in parallel
};
- Task(Mode mode, BlockImpl* block=NULL)
+ Task(Mode mode, BlockImpl* block = nullptr)
: _block(block)
, _mode(mode)
, _done_end(0)
@@ -45,23 +46,13 @@ public:
assert(!(mode == Mode::SINGLE && !block));
}
- Task& operator=(const Task& copy) {
- *static_cast<std::vector<Task>*>(this) = copy;
- _block = copy._block;
- _mode = copy._mode;
- _done_end = copy._done_end;
- _next = copy._next.load();
- _done = copy._done.load();
- return *this;
- }
-
- Task(const Task& copy)
- : std::vector<Task>(copy)
- , _block(copy._block)
- , _mode(copy._mode)
- , _done_end(copy._done_end)
- , _next(copy._next.load())
- , _done(copy._done.load())
+ Task(Task&& task)
+ : _children(std::move(task._children))
+ , _block(task._block)
+ , _mode(task._mode)
+ , _done_end(task._done_end)
+ , _next(task._next.load())
+ , _done(task._done.load())
{}
/** Run task in the given context. */
@@ -70,12 +61,20 @@ public:
/** Pretty print task to the given stream (recursively). */
void dump(std::function<void (const std::string&)> sink, unsigned indent, bool first) const;
+ /** Return true iff this is an empty task. */
+ bool empty() const { return _mode != Mode::SINGLE && _children.empty(); }
+
/** Simplify task expression. */
- void simplify();
+ static std::unique_ptr<Task> simplify(std::unique_ptr<Task>&& task);
/** Steal a child task from this task (succeeds for PARALLEL only). */
Task* steal(RunContext& context);
+ /** Prepend a child to this task. */
+ void push_front(Task&& task) {
+ _children.push_front(std::unique_ptr<Task>(new Task(std::move(task))));
+ }
+
Mode mode() const { return _mode; }
BlockImpl* block() const { return _block; }
bool done() const { return _done; }
@@ -83,8 +82,14 @@ public:
void set_done(bool done) { _done = done; }
private:
+ typedef std::deque<std::unique_ptr<Task>> Children;
+
+ Task(const Task&) = delete;
+ Task& operator=(const Task&) = delete;
+
Task* get_task(RunContext& context);
+ Children _children; ///< Vector of child tasks
BlockImpl* _block; ///< Used for SINGLE only
Mode _mode; ///< Execution mode
unsigned _done_end; ///< Index of rightmost done sub-task
diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp
index 9119ae14..00b59ca7 100644
--- a/src/server/ingen_lv2.cpp
+++ b/src/server/ingen_lv2.cpp
@@ -507,9 +507,9 @@ ingen_instantiate(const LV2_Descriptor* descriptor,
}
IngenPlugin* plugin = new IngenPlugin();
- plugin->map = map;
- plugin->world = new Ingen::World(
- plugin->argc, plugin->argv, map, unmap, log);
+ plugin->map = map;
+ plugin->world = new Ingen::World(map, unmap, log);
+ plugin->world->load_configuration(plugin->argc, plugin->argv);
LV2_URID bufsz_max = map->map(map->handle, LV2_BUF_SIZE__maxBlockLength);
LV2_URID bufsz_seq = map->map(map->handle, LV2_BUF_SIZE__sequenceSize);
diff --git a/tests/TestClient.hpp b/tests/TestClient.hpp
new file mode 100644
index 00000000..f1e0c5a7
--- /dev/null
+++ b/tests/TestClient.hpp
@@ -0,0 +1,90 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
+
+ Ingen is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or any later version.
+
+ Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Ingen. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_TESTCLIENT_HPP
+#define INGEN_TESTCLIENT_HPP
+
+#include "ingen/Interface.hpp"
+
+using namespace Ingen;
+
+class TestClient : public Ingen::Interface
+{
+public:
+ explicit TestClient(Log& log) : _log(log) {}
+ ~TestClient() {}
+
+ Raul::URI uri() const { return Raul::URI("ingen:testClient"); }
+
+ void bundle_begin() {}
+
+ void bundle_end() {}
+
+ void put(const Raul::URI& uri,
+ const Properties& properties,
+ Resource::Graph ctx = Resource::Graph::DEFAULT) {}
+
+ void delta(const Raul::URI& uri,
+ const Properties& remove,
+ const Properties& add) {}
+
+ void copy(const Raul::URI& old_uri,
+ const Raul::URI& new_uri) {}
+
+ void move(const Raul::Path& old_path,
+ const Raul::Path& new_path) {}
+
+ void del(const Raul::URI& uri) {}
+
+ void connect(const Raul::Path& tail,
+ const Raul::Path& head) {}
+
+ void disconnect(const Raul::Path& tail,
+ const Raul::Path& head) {}
+
+ void disconnect_all(const Raul::Path& parent_patch_path,
+ const Raul::Path& path) {}
+
+ void set_property(const Raul::URI& subject,
+ const Raul::URI& predicate,
+ const Atom& value) {}
+
+ void set_response_id(int32_t id) {}
+
+ void get(const Raul::URI& uri) {}
+
+ void undo() {}
+
+ void redo() {}
+
+ void response(int32_t id, Status status, const std::string& subject) {
+ if (status != Status::SUCCESS) {
+ _log.error(fmt("error on message %1%: %2% (%3%)\n")
+ % id % ingen_status_string(status) % subject);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ void error(const std::string& msg) {
+ _log.error(fmt("error: %1%\n") % msg);
+ exit(EXIT_FAILURE);
+ }
+
+private:
+ Log& _log;
+};
+
+#endif // INGEN_TESTCLIENT_HPP
diff --git a/tests/ingen_bench.cpp b/tests/ingen_bench.cpp
new file mode 100644
index 00000000..c8203331
--- /dev/null
+++ b/tests/ingen_bench.cpp
@@ -0,0 +1,189 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
+
+ Ingen is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or any later version.
+
+ Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Ingen. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <signal.h>
+#include <stdlib.h>
+
+#include <iostream>
+#include <string>
+#include <thread>
+
+#include <boost/optional.hpp>
+
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/thread.h>
+#include <glibmm/timer.h>
+
+#include "raul/Path.hpp"
+
+#include "serd/serd.h"
+#include "sord/sordmm.hpp"
+#include "sratom/sratom.h"
+
+#include "ingen_config.h"
+
+#include "ingen/AtomReader.hpp"
+#include "ingen/AtomWriter.hpp"
+#include "ingen/Configuration.hpp"
+#include "ingen/Configuration.hpp"
+#include "ingen/EngineBase.hpp"
+#include "ingen/Interface.hpp"
+#include "ingen/Parser.hpp"
+#include "ingen/Serialiser.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URIMap.hpp"
+#include "ingen/World.hpp"
+#include "ingen/client/ThreadedSigClientInterface.hpp"
+#include "ingen/runtime_paths.hpp"
+#include "ingen/types.hpp"
+#include "../src/server/Clock.hpp" // FIXME
+
+#include "TestClient.hpp"
+
+using namespace std;
+using namespace Ingen;
+
+World* world = NULL;
+
+static void
+ingen_try(bool cond, const char* msg)
+{
+ if (!cond) {
+ cerr << "ingen: Error: " << msg << endl;
+ delete world;
+ exit(EXIT_FAILURE);
+ }
+}
+
+static uint32_t
+flush_events(Ingen::World* world, const uint32_t start)
+{
+ static const uint32_t block_length = 4096;
+ int count = 0;
+ uint32_t offset = start;
+ while (world->engine()->pending_events()) {
+ world->engine()->locate(offset, block_length);
+ world->engine()->run(block_length);
+ world->engine()->main_iteration();
+ g_usleep(1000);
+ ++count;
+ offset += block_length;
+ }
+ return offset;
+}
+
+static std::string
+real_path(const char* path)
+{
+ char* const c_real_path = realpath(path, NULL);
+ const std::string result(c_real_path ? c_real_path : "");
+ free(c_real_path);
+ return result;
+}
+
+int
+main(int argc, char** argv)
+{
+ Glib::thread_init();
+ set_bundle_path_from_code((void*)&flush_events);
+
+ // Create world
+ try {
+ world = new World(NULL, NULL, NULL);
+ world->conf().add(
+ "output", "output", 'O', "File to write benchmark output",
+ Ingen::Configuration::SESSION, world->forge().String, Atom());
+ world->load_configuration(argc, argv);
+ } catch (std::exception& e) {
+ cout << "ingen: " << e.what() << endl;
+ return EXIT_FAILURE;
+ }
+
+ // Get mandatory command line arguments
+ const Atom& load = world->conf().option("load");
+ const Atom& out = world->conf().option("output");
+ if (!load.is_valid() || !out.is_valid()) {
+ cerr << "Usage: ingen_bench --load START_GRAPH --output OUT_FILE" << endl;
+ return EXIT_FAILURE;
+ }
+
+ // Get start graph and output file options
+ const std::string start_graph = real_path((const char*)load.get_body());
+ const std::string out_file = (const char*)out.get_body();
+ if (start_graph.empty()) {
+ cerr << "error: initial graph '"
+ << ((const char*)load.get_body())
+ << "' does not exist" << endl;
+ return EXIT_FAILURE;
+ }
+
+ // Load modules
+ ingen_try(world->load_module("server_profiled"),
+ "Unable to load server module");
+
+ // Initialise engine
+ ingen_try(bool(world->engine()),
+ "Unable to create engine");
+ world->engine()->init(48000.0, 4096, 4096);
+ world->engine()->activate();
+
+ // Load graph
+ if (!world->parser()->parse_file(world, world->interface().get(), start_graph)) {
+ cerr << "error: failed to load initial graph " << start_graph << endl;
+ return EXIT_FAILURE;
+ }
+ uint32_t time = flush_events(world, 0);
+
+ Ingen::Server::Clock clock;
+ static const uint32_t n_test_frames = 1 << 20;
+ static const uint32_t block_length = 4096;
+
+ // Set up real-time scheduling for this (main run) thread
+ pthread_t pthread = pthread_self();
+ const int policy = SCHED_FIFO;
+ sched_param sp;
+ sp.sched_priority = 90;
+ if (pthread_setschedparam(pthread, policy, &sp)) {
+ world->log().error(
+ fmt("Failed to set real-time priority of run thread (%s)\n")
+ % strerror(errno));
+ }
+
+ const uint64_t t_start = clock.now_microseconds();
+ for (uint32_t i = time; i < time + n_test_frames; i += block_length) {
+ world->engine()->locate(i, block_length);
+ world->engine()->run(block_length);
+ //world->engine()->main_iteration();
+ }
+ const uint64_t t_end = clock.now_microseconds();
+
+ FILE* log = fopen(out_file.c_str(), "a");
+ if (ftell(log) == 0) {
+ fprintf(log, "n_threads\trun_time\tn_frames\n");
+ }
+ fprintf(log, "%u\t%llu\t%u\n",
+ world->conf().option("threads").get<int32_t>(),
+ t_end - t_start,
+ n_test_frames);
+ fclose(log);
+
+ // Shut down
+ world->engine()->deactivate();
+
+ delete world;
+ return EXIT_SUCCESS;
+}
diff --git a/tests/ingen_test.cpp b/tests/ingen_test.cpp
index 1ac2e383..d55789db 100644
--- a/tests/ingen_test.cpp
+++ b/tests/ingen_test.cpp
@@ -51,78 +51,13 @@
#include "ingen/runtime_paths.hpp"
#include "ingen/types.hpp"
+#include "TestClient.hpp"
+
using namespace std;
using namespace Ingen;
World* world = NULL;
-class TestClient : public Interface
-{
-public:
- explicit TestClient(Log& log) : _log(log) {}
- ~TestClient() {}
-
- Raul::URI uri() const { return Raul::URI("ingen:testClient"); }
-
- void bundle_begin() {}
-
- void bundle_end() {}
-
- void put(const Raul::URI& uri,
- const Properties& properties,
- Resource::Graph ctx = Resource::Graph::DEFAULT) {}
-
- void delta(const Raul::URI& uri,
- const Properties& remove,
- const Properties& add) {}
-
- void copy(const Raul::URI& old_uri,
- const Raul::URI& new_uri) {}
-
- void move(const Raul::Path& old_path,
- const Raul::Path& new_path) {}
-
- void del(const Raul::URI& uri) {}
-
- void connect(const Raul::Path& tail,
- const Raul::Path& head) {}
-
- void disconnect(const Raul::Path& tail,
- const Raul::Path& head) {}
-
- void disconnect_all(const Raul::Path& parent_patch_path,
- const Raul::Path& path) {}
-
- void set_property(const Raul::URI& subject,
- const Raul::URI& predicate,
- const Atom& value) {}
-
- void set_response_id(int32_t id) {}
-
- void get(const Raul::URI& uri) {}
-
- void undo() {}
-
- void redo() {}
-
- void response(int32_t id, Status status, const std::string& subject) {
- if (status != Status::SUCCESS) {
- _log.error(fmt("error on message %1%: %2% (%3%)\n")
- % id % ingen_status_string(status) % subject);
- exit(EXIT_FAILURE);
- }
- }
-
- void error(const std::string& msg) {
- _log.error(fmt("error: %1%\n") % msg);
- exit(EXIT_FAILURE);
- }
-
-private:
- Log& _log;
-};
-
-
static void
ingen_try(bool cond, const char* msg)
{
@@ -158,7 +93,8 @@ main(int argc, char** argv)
// Create world
try {
- world = new World(argc, argv, NULL, NULL, NULL);
+ world = new World(NULL, NULL, NULL);
+ world->load_configuration(argc, argv);
} catch (std::exception& e) {
cout << "ingen: " << e.what() << endl;
return EXIT_FAILURE;
@@ -194,7 +130,7 @@ main(int argc, char** argv)
world->engine()->init(48000.0, 4096, 4096);
world->engine()->activate();
- // Load patch
+ // Load graph
if (!world->parser()->parse_file(world, world->interface().get(), start_graph)) {
cerr << "error: failed to load initial graph " << start_graph << endl;
return EXIT_FAILURE;
diff --git a/wscript b/wscript
index 538e2b84..5ff3f4ba 100644
--- a/wscript
+++ b/wscript
@@ -234,15 +234,16 @@ def build(bld):
# Test program
if bld.env.BUILD_TESTS:
- obj = bld(features = 'cxx cxxprogram',
- source = 'tests/ingen_test.cpp',
- target = 'tests/ingen_test',
- includes = ['.'],
- use = 'libingen_profiled',
- install_path = '',
- cxxflags = bld.env.INGEN_TEST_CXXFLAGS,
- linkflags = bld.env.INGEN_TEST_LINKFLAGS)
- autowaf.use_lib(bld, obj, 'GTHREAD GLIBMM SORD RAUL LILV INGEN LV2 SRATOM')
+ for i in ['ingen_test', 'ingen_bench']:
+ obj = bld(features = 'cxx cxxprogram',
+ source = 'tests/%s.cpp' % i,
+ target = 'tests/%s' % i,
+ includes = ['.'],
+ use = 'libingen_profiled',
+ install_path = '',
+ cxxflags = bld.env.INGEN_TEST_CXXFLAGS,
+ linkflags = bld.env.INGEN_TEST_LINKFLAGS)
+ autowaf.use_lib(bld, obj, 'GTHREAD GLIBMM SORD RAUL LILV INGEN LV2 SRATOM')
bld.install_files('${DATADIR}/applications', 'src/ingen/ingen.desktop')
bld.install_files('${BINDIR}', 'scripts/ingenish', chmod=Utils.O755)