Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster device initialization by multi-threading. #519

Merged
merged 8 commits into from
Nov 6, 2024
71 changes: 63 additions & 8 deletions MMCore/MMCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@
#include <cassert>
#include <chrono>
#include <cstring>
#include <deque>
#include <fstream>
#include <future>
#include <map>
#include <set>
#include <sstream>
#include <thread>
#include <vector>

#ifdef _MSC_VER
Expand Down Expand Up @@ -108,7 +112,7 @@
* (Keep the 3 numbers on one line to make it easier to look at diffs when
* merging/rebasing.)
*/
const int MMCore_versionMajor = 11, MMCore_versionMinor = 1, MMCore_versionPatch = 1;
const int MMCore_versionMajor = 11, MMCore_versionMinor = 2, MMCore_versionPatch = 1;


///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -842,15 +846,19 @@ void CMMCore::reset() throw (CMMError)

/**
* Calls Initialize() method for each loaded device.
* This method also initialized allowed values for core properties, based
* This implementation initializes devices on separate threads, one per device module (adapter).
* This method also initializes allowed values for core properties, based
* on the collection of loaded devices.
*/
void CMMCore::initializeAllDevices() throw (CMMError)
{
std::vector<std::string> devices = deviceManager_->GetDeviceList();
LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices";

std::map<std::shared_ptr<LoadedDeviceAdapter>, std::deque<std::pair<std::shared_ptr<DeviceInstance>, std::string>>> moduleMap;

for (size_t i=0; i<devices.size(); i++)
// first round, collect all DeviceAdapters
for (size_t i = 0; i < devices.size(); i++)
{
std::shared_ptr<DeviceInstance> pDevice;
try {
Expand All @@ -860,19 +868,66 @@ void CMMCore::initializeAllDevices() throw (CMMError)
logError(devices[i].c_str(), err.getMsg().c_str());
throw;
}
mm::DeviceModuleLockGuard guard(pDevice);
LOG_INFO(coreLogger_) << "Will initialize device " << devices[i];
pDevice->Initialize();
LOG_INFO(coreLogger_) << "Did initialize device " << devices[i];
std::shared_ptr<LoadedDeviceAdapter> pAdapter;
pAdapter = pDevice->GetAdapterModule();

assignDefaultRole(pDevice);
if (moduleMap.find(pAdapter) == moduleMap.end())
{
std::deque<std::pair<std::shared_ptr<DeviceInstance>, std::string>> pDevices;
pDevices.push_back(make_pair(pDevice, devices[i]));
moduleMap.insert({ pAdapter, pDevices });
}
else
{
moduleMap.find(pAdapter)->second.push_back(make_pair(pDevice, devices[i]));
}
}

// second round, spin up threads to initialize devices, one thread per module
std::vector<std::future<int>> futures;
std::map<std::shared_ptr<LoadedDeviceAdapter>, std::deque<std::pair<std::shared_ptr<DeviceInstance>, std::string>>>::iterator it;
for (it = moduleMap.begin(); it != moduleMap.end(); it++)
{
auto f = std::async(std::launch::async, &CMMCore::initializeDequeOfDevices, this, it->second);
futures.push_back(std::move(f));
}
for (auto& f : futures) {
// Note: we can do a 'f.wait_for(std::chrono::seconds(20)' to wait up to 20 seconds before giving up
// which would avoid hanging with devices that hang in their initialize function
f.get();
}

// assign default roles syncronously
for (it = moduleMap.begin(); it != moduleMap.end(); it++)
{
std::deque<std::pair<std::shared_ptr<DeviceInstance>, std::string>> pDevices = it->second;
for (int i = 0; i < pDevices.size(); i++)
{
assignDefaultRole(pDevices[i].first);
}
}
LOG_INFO(coreLogger_) << "Finished initializing " << devices.size() << " devices";

updateCoreProperties();
}


/**
* This helper function is executed by a single thread, allowing initializeAllDevices to operate multi-threaded.
* All devices are supposed to originate from the same device adapter
*/
int CMMCore::initializeDequeOfDevices(std::deque<std::pair<std::shared_ptr<DeviceInstance>, std::string>> pDevices) {
for (int i = 0; i < pDevices.size(); i++) {
std::shared_ptr<DeviceInstance> pDevice = pDevices[i].first;

mm::DeviceModuleLockGuard guard(pDevice);
LOG_INFO(coreLogger_) << "Will initialize device " << pDevices[i].second;
pDevice->Initialize();
LOG_INFO(coreLogger_) << "Did initialize device " << pDevices[i].second;
}
return DEVICE_OK;
}

/**
* Updates CoreProperties (currently all Core properties are
* devices types) with the loaded hardware.
Expand Down
1 change: 1 addition & 0 deletions MMCore/MMCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ class CMMCore
void assignDefaultRole(std::shared_ptr<DeviceInstance> pDev);
void updateCoreProperty(const char* propName, MM::DeviceType devType) throw (CMMError);
void loadSystemConfigurationImpl(const char* fileName) throw (CMMError);
int initializeDequeOfDevices(std::deque<std::pair<std::shared_ptr<DeviceInstance>, std::string>> pDevices);
};

#if defined(__GNUC__) && !defined(__clang__)
Expand Down
Loading