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

Conversation

nicost
Copy link
Member

@nicost nicost commented Oct 29, 2024

This PR changes the behavior of the Core function initializeAllDevices. It will discover all device modules contained in the list of devices to be initialized, spin up one thread per module and initialize all requested devices in that module. This parallelizes device initialization which can improve startup times of the Micro-Manager application considerably. Tested with a limited number of devices, I will test on a few more microscopes before merging, but requesting review (especially since I am not very experienced writing multi-threaded code in C++) now.

Minor version of Studio was upgraded according to Semver. I will wait for review before merging.

@nicost nicost requested a review from marktsuchida October 29, 2024 20:11
Copy link
Member

@marktsuchida marktsuchida left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I certainly like the idea. One thing that will be essential is a mechanism to order initialization of devices that depend on other devices (that are not from the same adapter).

The most prominent case of this is serial (and other) ports. (Perhaps this could be special-cased, though somewhat ugly.)

But I would like to see a comprehensive check to make sure that there are no other cases -- for example in some of the Utility devices, and any other that "wrap" another device(s). If it can be shown that no existing device depends on another device being initialized before it (as opposed to merely being loaded), then it's okay; otherwise this can only work by adding a mechanism to somehow represent the dependencies.

Beyond that, I cannot help but note that there is no mechanism to protect access to the core from multiple threads. Devices can call Core functions (through CoreCallback) during initialization, and we need to be sure that none of these cause race conditions. I'd be opposed to merging this without addressing this, even if it appears to work in limited testing. (It's possible that this turns out not to be an issue, or that it can be made safe in the cases where it matters.)

Another thought is that it might be good to make the parallel loading optional, even if going forward with this, so that people can disable it if it is later found to cause problems.

@nicost
Copy link
Member Author

nicost commented Oct 30, 2024

Ports should initialize first for sure. Great catch!

I believe that all Utility devices only start communicating with their "target" devices after initialization (I ran into issue with this in the past). Order has never been guaranteed (depended on the config file order), so any adapter that does depend on it should be fixed.

Happy to make this optional, but where should the option live? Would this be a Core Property? I will check when those are set, obviously should happen before initializing the devices or it would not work.

@tlambert03
Copy link
Contributor

Seems like a good candidate for the new feature flag pattern?

…lt. Setting it false will use the old code to do serial initialization.

Also ensure that ports are initialized first.
Needs testing on several systems before merging.
@marktsuchida
Copy link
Member

Order has never been guaranteed (depended on the config file order), so any adapter that does depend on it should be fixed.

Yes, but loading in the config file order (upon which the user has control) was guaranteed. I think we depended on this at least at some point in the past, but I agree that Utilities now look safe.

In any case, I think it's okay if we've missed some extremely obscure case (I couldn't find any in 15 minutes) as long as it can be turned off (which will require a checkbox on the splash screen after this PR is merged).

Comment on lines 966 to 970
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();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The std::future returned by std::async is special and its destructor blocks until the future completes. This is okay if there are 0 or 1 errors total (the successful initializations run to completion and the exception is propagated).

When there are 2 or more errors, however, the second exception would be thrown in the destructor of the future, and throwing anything in a destructor is very bad (might terminate by default).

I think it's best to catch the first exception and then explicitly wait for the remaining futures (catching and ignoring any subsequent exceptions). Then the first exception can be rethrown. (std::current_exception and std::rethrow_exception can be used.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented as you described in a52a350. Tested with limited hardware (guess we need multiple demo devices that can throw exceptions in the initialize function to fully test this).

Also tested earlier version on complicated hardware, where it reduced startup times from 32 to 20 seconds, which is an appreciable improvement.

… clear the used vectors at the end of the function.

I am not quite sure if this is needed, but after this change, I no longer
saw an exception on Micro-Manager shutdown, so it may be helpful.
…alize all other devices, ignoring more exceptions if they occur, then rethrow first exception.
Copy link
Member

@marktsuchida marktsuchida left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@nicost nicost merged commit 26d3716 into micro-manager:main Nov 6, 2024
3 of 4 checks passed
@nicost nicost deleted the fasterDeviceInitialization branch November 15, 2024 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants