You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
match_features runs on a threading backend — the GIL caps pair matching at ~2 effective cores regardless of processes #52
ODM 3.6.2, official docker image (opendronemap/odm@sha256:a96f56db…), joblib 1.5.3
16-core Intel (Panther Lake), 30 GB RAM, CPU-only
704 images (4K video frames), --feature-quality ultra, --min-num-features 20000,
HAHOG + WORDS (BOW) matcher, no/partial GPS → 247,456 candidate pairs
Observed behavior
match_features logs Computing pair matching with 12 processes, but host CPU
utilization stays at ~2 cores. Measured throughput: 362 pairs/min
(≈ 11.4 h for this dataset's matching phase alone).
Cause
opensfm/context.py::parallel_map runs everything through joblib's threading backend:
So processes: N is actually N threads serialized on the Python GIL. The
C++ parts (FLANN/OpenCV) release the GIL only partially; the Python glue
(feature loading/decompression, candidate filtering) does not scale.
Results on the dataset above (container memory limit 18 GiB):
backend
workers
pairs/min
outcome
threading
"12"
362
stable; ~2 effective cores
loky
12
—
workers OOM-SIGKILLed after ~4 min
loky
6
—
OOM after ~90 s
loky
4
570
ran at the memory limit (reclaim thrash, per-worker rate degraded), OOM after ~10 min
loky
2
508
stable at ~11 GiB total; 1.4× threading
Key numbers: one loky worker sustains ~256 pairs/min — i.e. 12 threads
together deliver about 1.4 worker-equivalents. Per worker, real processes
are ~2.5× faster than the threading pool's aggregate.
Why processes alone don't scale further
With fork/spawn workers nothing is shared: each worker loads its own copies of
feature .npz files and builds its own FLANN indexes, so memory scales
linearly with workers (~5 GiB/worker on ultra features here) and >2–3 workers
exceed a 30 GB machine.
Suggestions (in increasing order of impact)
Expose the backend at the match_features call site (config option or
env var), documenting the memory trade-off. One-line change; on machines
with enough RAM this alone is a big win.
Share the feature store between workers (e.g. memory-mapped arrays / joblib memmapping instead of per-worker npz loading).
Cache FLANN indexes per image (bounded LRU) instead of rebuilding per
pair — helps both backends.
With (2)+(3), 12 real workers would fit comfortably in RAM; extrapolating the
per-worker rate, matching for this dataset would drop from ~11 h to roughly
1–1.5 h.
Dear @haraldschilly please note that we are in the middle of a migration; ODM 3.6.2 is using a legacy OpenSfM copy.
The next version will likely bring a lot of changes, as we will incorporate all the changes from upstream (OpenSfM/OpenSfM), with a lot of contributions (kudos to Yann N. for pulling this one)
So, if I may, can we push this down the road and see if it still reproduces under OpenSfM v1.1
Activity
DodgySpaniard commentedon Aug 25, 2026
Dear @haraldschilly please note that we are in the middle of a migration; ODM 3.6.2 is using a legacy OpenSfM copy.
The next version will likely bring a lot of changes, as we will incorporate all the changes from upstream (OpenSfM/OpenSfM), with a lot of contributions (kudos to Yann N. for pulling this one)
So, if I may, can we push this down the road and see if it still reproduces under OpenSfM v1.1
haraldschilly commentedon Aug 25, 2026
@DodgySpaniard yes sure