Score the first-pass validation frames together
Two-pass rotation indexing picks between candidate lattices by forcing each one and counting how many of 60 validation frames it indexes. That count was a serial loop, and it is the single largest serial stretch outside scale/merge: 57% of the 4.9 s first-pass phase, a few Ceres solves per frame on one core while the other 47 and all four GPUs sit idle. The frames are scored together now. Each one's verdict is its own - the score is only how many index - and this is the same call the main image loop already makes from every one of its workers on this same IndexAndRefine, which writes nothing but unit_cells[] under its own mutex. With the candidate forced, GetLattice() returns it and the branch that would advance the indexer's own state is never reached. The offline solver stops on an iteration count rather than a clock, so a loaded machine cannot change a frame's verdict. The spot cache had to be filled first: it is a plain map filled on demand, and a lookup racing an insert is not something a map survives. Filling it stays serial and in frame order, so its contents do not depend on scheduling. First-pass scheme gaps on the heaviest crystal 4.93 s -> 2.06 s, with both schemes returning the same 60/60 they did before. Battery 9m23s -> 9m01s, space group 21/24, no failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
83b33e19ee
commit
312df30463
+22
-7
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "../reader/JFJochHDF5Reader.h"
|
||||
#include "../common/JFJochMath.h"
|
||||
#include "../common/ParallelFor.h"
|
||||
#include "../common/Logger.h"
|
||||
#include "../common/AzimuthalIntegrationMapping.h"
|
||||
#include "../common/AzimuthalIntegrationProfile.h"
|
||||
@@ -1290,15 +1291,29 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
|
||||
validation_settings.quick_integration = false;
|
||||
auto count_indexed = [&](IndexAndRefine &idx, const RotationIndexerResult &r) -> int {
|
||||
idx.ForceRotationIndexerResult(r);
|
||||
int count = 0;
|
||||
for (const int ordinal : validation) {
|
||||
// Fill the spot cache first. It is a plain map filled on demand, and the loop below runs the
|
||||
// frames in parallel, where a lookup racing an insert is not something the map survives.
|
||||
// Spot finding stays serial and in frame order, so what ends up in the cache does not depend
|
||||
// on scheduling; after the first candidate this costs nothing.
|
||||
for (const int ordinal : validation)
|
||||
get_spots(ordinal);
|
||||
// Each frame's verdict is its own - the score is just how many of them index - so they run
|
||||
// together. Refining one frame's geometry is a few Ceres solves and no GPU, and it is the
|
||||
// same call the main image loop already makes from every one of its workers on this same
|
||||
// IndexAndRefine, writing only unit_cells[] under its own mutex. With the candidate forced,
|
||||
// GetLattice() returns it and the branch that would advance the indexer's own state is never
|
||||
// reached. The offline solver stops on an iteration count rather than a clock
|
||||
// (IndexAndRefine::RefineGeometryIfNeeded), so a loaded machine cannot change a verdict.
|
||||
std::atomic<int> count = 0;
|
||||
ParallelFor(static_cast<int>(validation.size()),
|
||||
std::min<size_t>(validation.size(), config_.nthreads), [&](int i) {
|
||||
DataMessage m{};
|
||||
m.number = ordinal;
|
||||
m.spots = get_spots(ordinal);
|
||||
m.number = validation[i];
|
||||
m.spots = spot_cache.at(validation[i]);
|
||||
if (idx.IndexFrameOnly(m, validation_settings))
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
++count;
|
||||
});
|
||||
return count.load();
|
||||
};
|
||||
|
||||
// Feed one first-pass scheme (a set of image ordinals) into its own rotation indexer, ready to
|
||||
|
||||
Reference in New Issue
Block a user