Prepare for parallel Disk I/O

This commit is contained in:
Robin Gareus 2024-04-30 00:01:32 +02:00
parent 2af2df3516
commit 170b915038
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
7 changed files with 155 additions and 27 deletions

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2024 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _ardour_io_tasklist_h_
#define _ardour_io_tasklist_h_
#include <boost/function.hpp>
#include <vector>
#include "ardour/libardour_visibility.h"
namespace ARDOUR
{
class LIBARDOUR_API IOTaskList
{
public:
IOTaskList ();
~IOTaskList ();
/** process tasks in list in parallel, wait for them to complete */
void process ();
void push_back (boost::function<void ()> fn);
private:
std::vector<boost::function<void ()>> _tasks;
size_t _n_threads;
};
} // namespace ARDOUR
#endif

View File

@ -149,6 +149,7 @@ struct GraphChain;
class IO;
class IOPlug;
class IOProcessor;
class IOTaskList;
class ImportStatus;
class MidiClockTicker;
class MidiControlUI;
@ -334,6 +335,7 @@ public:
}
std::shared_ptr<RTTaskList> rt_tasklist () { return _rt_tasklist; }
std::shared_ptr<IOTaskList> io_tasklist () { return _io_tasklist; }
RouteList get_routelist (bool mixer_order = false, PresentationInfo::Flag fl = PresentationInfo::MixerRoutes) const;
@ -2344,6 +2346,7 @@ private:
std::shared_ptr<Port> _ltc_output_port;
std::shared_ptr<RTTaskList> _rt_tasklist;
std::shared_ptr<IOTaskList> _io_tasklist;
/* Scene Changing */
SceneChanger* _scene_changer;

View File

@ -44,6 +44,7 @@
#include "ardour/disk_io.h"
#include "ardour/disk_reader.h"
#include "ardour/io.h"
#include "ardour/io_tasklist.h"
#include "ardour/session.h"
#include "ardour/track.h"
@ -264,6 +265,8 @@ Butler::thread_work ()
DEBUG_TRACE (DEBUG::Butler, string_compose ("butler starts refill loop, twr = %1\n", transport_work_requested ()));
std::shared_ptr<IOTaskList> tl = _session.io_tasklist ();
for (i = rl_with_auditioner.begin (); !transport_work_requested () && should_run && i != rl_with_auditioner.end (); ++i) {
std::shared_ptr<Track> tr = std::dynamic_pointer_cast<Track> (*i);
@ -278,24 +281,27 @@ Butler::thread_work ()
// DEBUG_TRACE (DEBUG::Butler, string_compose ("butler skips inactive track %1\n", tr->name()));
continue;
}
// DEBUG_TRACE (DEBUG::Butler, string_compose ("butler refills %1, playback load = %2\n", tr->name(), tr->playback_buffer_load()));
switch (tr->do_refill ()) {
case 0:
//DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill done %1\n", tr->name()));
break;
case 1:
DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill unfinished %1\n", tr->name ()));
disk_work_outstanding = true;
break;
default:
error << string_compose (_("Butler read ahead failure on dstream %1"), (*i)->name ()) << endmsg;
std::cerr << string_compose (_("Butler read ahead failure on dstream %1"), (*i)->name ()) << std::endl;
break;
}
tl->push_back ([tr, &disk_work_outstanding]() {
switch (tr->do_refill ()) {
case 0:
//DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill done %1\n", tr->name()));
break;
case 1:
DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill unfinished %1\n", tr->name ()));
disk_work_outstanding = true;
break;
default:
error << string_compose (_("Butler read ahead failure on dstream %1"), tr->name ()) << endmsg;
std::cerr << string_compose (_("Butler read ahead failure on dstream %1"), tr->name ()) << std::endl;
break;
}
});
}
tl->process ();
tl.reset ();
if (i != rl_with_auditioner.begin () && i != rl_with_auditioner.end ()) {
/* we didn't get to all the streams */
disk_work_outstanding = true;

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2017-2024 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "pbd/pthread_utils.h"
#include "ardour/io_tasklist.h"
using namespace ARDOUR;
IOTaskList::IOTaskList ()
: _n_threads (0)
{
}
IOTaskList::~IOTaskList ()
{
}
void
IOTaskList::push_back (boost::function<void ()> fn)
{
_tasks.push_back (fn);
}
void
IOTaskList::process ()
{
assert (strcmp (pthread_name (), "butler") == 0);
//std::cout << "IOTaskList::process " << pthread_name () << " " << _tasks.size () << "\n";
if (_n_threads > 1 && _tasks.size () > 2) {
// TODO
} else {
for (auto const& fn : _tasks) {
fn ();
}
}
_tasks.clear ();
}

View File

@ -85,6 +85,7 @@
#include "ardour/gain_control.h"
#include "ardour/graph.h"
#include "ardour/io_plug.h"
#include "ardour/io_tasklist.h"
#include "ardour/luabindings.h"
#include "ardour/lv2_plugin.h"
#include "ardour/midiport_manager.h"
@ -589,6 +590,7 @@ Session::immediately_post_engine ()
_process_graph.reset (new Graph (*this));
_rt_tasklist.reset (new RTTaskList (_process_graph));
_io_tasklist.reset (new IOTaskList ());
/* every time we reconnect, recompute worst case output latencies */
@ -720,6 +722,8 @@ Session::destroy ()
_io_graph_chain[0].reset ();
_io_graph_chain[1].reset ();
_io_tasklist.reset ();
_butler->drop_references ();
delete _butler;
_butler = 0;

View File

@ -54,6 +54,7 @@
#include "ardour/click.h"
#include "ardour/debug.h"
#include "ardour/disk_reader.h"
#include "ardour/io_tasklist.h"
#include "ardour/location.h"
#include "ardour/playlist.h"
#include "ardour/profile.h"
@ -1283,12 +1284,14 @@ Session::non_realtime_locate ()
tf = _transport_sample;
start = get_microseconds ();
std::shared_ptr<IOTaskList> tl = io_tasklist ();
for (auto const& i : *rl) {
++nt;
i->non_realtime_locate (tf);
if (sc != _seek_counter.load ()) {
goto restart;
}
tl->push_back ([this, i, tf, sc]() { if (sc == _seek_counter.load ()) { i->non_realtime_locate (tf); }});
}
tl->process ();
if (sc != _seek_counter.load ()) {
goto restart;
}
microseconds_t end = get_microseconds ();
@ -1528,15 +1531,26 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished, bool will_
DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: locate\n"));
for (auto const& i : *r) {
DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", i->name()));
i->non_realtime_locate (_transport_sample);
std::shared_ptr<IOTaskList> tl = io_tasklist ();
if (on_entry != _butler->should_do_transport_work.load()) {
finished = false;
/* we will be back */
return;
}
std::atomic<bool> fini (finished);
for (auto const& i : *r) {
tl->push_back ([this, i, on_entry, &fini]() {
if (!fini.load ()) {
return;
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", i->name()));
i->non_realtime_locate (_transport_sample);
if (on_entry != _butler->should_do_transport_work.load()) {
fini = false;
}
});
}
tl->process ();
if (!fini.load ()) {
finished = false;
/* we will be back */
return;
}
VCAList v = _vca_manager->vcas ();

View File

@ -110,6 +110,7 @@ libardour_sources = [
'io.cc',
'io_plug.cc',
'io_processor.cc',
'io_tasklist.cc',
'kmeterdsp.cc',
'ladspa_plugin.cc',
'latent.cc',