fix: add threaded ApplySearch workers - #2373
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new threaded ApplySearch behavior lacks focused regression coverage for worker dispatch, output parity, and argument rejection.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds threaded inverse-search processing for iccApplySearch, addressing #2348.
Changes:
- Adds
-threads NCLI support and batched application. - Creates independent search apply contexts for worker safety.
- Extends IccConnect APIs and threading documentation.
File summaries
| File | Description |
|---|---|
Tools/CmdLine/IccApplySearch/iccApplySearch.cpp |
Adds thread parsing and batch processing. |
IccProfLib/IccCmmThread.h |
Exposes the wrapped base CMM. |
IccProfLib/IccCmmSearch.h |
Adds worker-ready search apply state. |
IccProfLib/IccCmmSearch.cpp |
Implements independent apply contexts. |
IccConnect/IccLibConnect/IccConnect.h |
Declares threaded search factory overload. |
IccConnect/IccLibConnect/IccConnect.cpp |
Wraps search CMMs for threaded execution. |
docs/tools-cli-reference.md |
Documents the CLI option. |
docs/icc-connect.md |
Documents the factory and accessors. |
docs/icc-cmm-threading.md |
Extends threading guidance to ApplySearch. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Balanced
Prior Review from colourbill-ctrl
Review — all 11 Copilot findings resolved at
|
| finding | at 677657cc |
|---|---|
IccCmmSearch.cpp:427 m_bValid guard leaves m_pApply null |
fixed — the bAllocNewApply && !m_pApply block allocates on the second call, and checkSearchDeferredApply() covers exactly that sequence |
| script: 46 reps only reach 3 workers | fixed — 49 copies, 4 workers measured below |
bare -threads / -threads '' fail open |
fixed — bare hits the argc < 3 guard; '' hits end == argv[2] |
GetSearchCmm() doc contract |
fixed in icc-connect.md and icc-cmm-threading.md |
pErrorMsg empty on attach failure |
fixed — "failed to attach search CMM" |
std::nothrow exception safety |
fixed — both reserve()s precede every release(), so a throwing reallocation unwinds through the unique_ptrs |
CreateSearch bounds check uncovered |
fixed — iccconnect-threaded-cmm.cpp:50-56 asserts nullptr + the message |
| 3x docs missing the upper bound | fixed |
Concurrency evidence
The packet the review kept asking for. Clang ThreadSanitizer, -DENABLE_TSAN=ON, Debug;
instrumentation verified (-fsanitize=thread in flags.make, 649 __tsan symbols) rather
than assumed from the cache variable.
Fixture is the script's own: rgb8bit.txt, 49 copies, sRGB_v4_ICC_preference.icc both legs.
| run | rc | TSan warnings | output |
|---|---|---|---|
| default | 0 | 0 | reference |
-threads 1 |
0 | 0 | byte-identical |
-threads 4 |
0 | 0 | byte-identical |
ICC_PERF_STATS_FILE confirms the dispatch is real, not a fallback:
-threads 1 threaded_calls=0 threaded_pixels=0 threaded_active_workers=0
-threads 4 threaded_calls=1 threaded_pixels=784 threaded_active_workers=4
(threaded_worker_strips=3 is activeWorkers - 1 by definition -- three queued, the caller
runs the fourth. Not a shortfall.)
The one gap: the fixture size is unpinned
cmp-ing threaded output against scalar output cannot detect under-dispatch, because
identical output is exactly what the threading is supposed to produce. Measured, same binary:
| copies | threaded_pixels |
threaded_active_workers |
script assertion |
|---|---|---|---|
| 1 | 0 | 0 | passes |
| 46 | 736 | 3 | passes |
| 49 | 784 | 4 | passes |
Zero threading passes as cleanly as four workers. So the 46 -> 49 bump fixes today's coverage
but nothing holds it there: 49 is coupled to kIccSmallApplyPixelsPerThread = 256, a
file-static in IccCmmThread.cpp -- a different translation unit, with no assertion tying the
two together. Raise that constant, or shrink the fixture, and this test silently stops
exercising threading while staying green.
Two ways to pin it, either fine:
- Assert dispatch directly -- set
ICC_PERF_STATS_FILEin the script and require
threaded_active_workers >= 4for the-threads 4case. The mechanism is already in tree
and analysis: add bounded MCS apply profiling #2363 uses it. - Derive the copy count from the constant instead of hardcoding it, so the fixture follows
the dispatch size automatically.
Same shape as #2150: an anti-vacuity pin has to be relative to what it guards, never an
absolute threshold.
The failing check is infra
Docker Clang 22 Verification fails at step 7, Pull trusted base image, 8s wall. That is the
GHCR base-image pin rotating out, not this branch -- a code failure does not die before the
build starts. Re-run once the pin is refreshed.
Nothing here blocks from my side once the fixture pin is decided.
Pre Merge Report2026-09-03 15:04:10 UTC This PR originates from #2362 which received Maintainer Review as Pasted above. This type of PR should always receive Human Review as Threading & Ownership are the primary issues. This PR followed the Pre-PR Review, Pre-Commit Hook & Pre-Push Security Cycles, clears all implemented Hurdles in ci-pr-action, ci-vcpkg-ports & CI Comprehensive Build and Test. Final checks in Docker container using ghcr.io/internationalcolorconsortium/iccdev:latest at a412656d8938bac62917173139dce712010529530055258bf78778390e2a2db9 IntentMerge then continue to next PR which adds the expected CTests & Documentation Updates. Additional updates to include vcpkg SHA update and any workflow or dependency updates needed to avoid workflow failures. |
QA Checks2026-09-03 15:28:42 UTC In https://github.com/InternationalColorConsortium/iccDEV/actions/runs/33770806366 this CTest looks too long than expected, but its mid-morning on a Thursday US East. Testing as A/B Test at master:HEAD and PR Branch HEAD. Research CTest148/243 Test #148: iccdev.hybrid-pipeline ......................................... Passed 700.09 sec The Code touches the Testing/hybrid/BuildandTest.sh for Phase 5 & 6. Local Testing indicates the expected Results. Summary - Its a busy day on CI |
WSL2 Local Build Time CompareHostIntel Xeon w5-2465X master:HEAD build timeStart: 2026-09-03 15:42:01 UTC PR Branch Build timeStart: 2026-09-03 15:34:04 UTC Repro |
Codex ReportLocal
|
| Threads | Median elapsed | Range | Speedup vs 1 thread |
|---|---|---|---|
| 1 | 3.30 s | 3.10-3.40 s | 1.00x |
| 2 | 2.20 s | 2.10-2.20 s | 1.50x |
| 4 | 1.50 s | 1.40-1.50 s | 2.20x |
| 8 | 1.10 s | 1.10-1.10 s | 3.00x |
All 28 executions passed and produced byte-identical output:
39ae384f7084d1aa81a212c08646c27b6cce6c83215d3336b09d97f679f47443
No stderr or sanitizer diagnostics were emitted.
Portable, AVX2, and AVX-512 coverage
Independent Clang Release builds ran iccdev.clut-eight-output-regression five times each:
| Variant | Result | Median end-to-end time |
|---|---|---|
| Portable | 5/5 passed | 0.12 s |
| AVX2 | 5/5 passed | 0.11 s |
| AVX-512 | 5/5 passed | 0.12 s |
The separate iccdev.clut-avx512-sixteen-output parity test also passed. These short CLUT measurements establish cross-ISA correctness coverage; they are not evidence that one ISA is faster than another.
Finding
The direct workload confirms useful iccApplySearch scaling through eight active workers, reaching 3.00x lower elapsed time at eight threads with identical output. No functional, sanitizer, or ISA-specific regression was observed. Results are local and workload-specific.
colourbill-ctrl
left a comment
There was a problem hiding this comment.
Reviewed at 971f4cf6. Threading and ownership hold: each worker owns private apply objects per sub-CMM, the sub-CMMs are read-only after Begin(), and the GetSearchCmm() unwrap is load-bearing -- iccApplySearch.cpp:472 would dereference null without it. Built clean on gcc, TSan and ASan+LSan; master's five existing CTests pass against it, four TSan-clean; output byte-identical at 1/4/8/auto across five input shapes including -cfg; 8.8s -> 2.0s at auto on 14 cores. 3df113ca was the right call -- clearing m_bValid would send a retry back through a body that is not re-runnable.
Approving. The #2362 test files apply clean on this head if you want them as PR 2; PR-2 notes (a threshold line in the CLI docs, a size assert in the batch path) go in a follow-up issue rather than here.
Post Merge Report2026-09-03 19:17:34 UTC
Opening an Issue now... |
PR Summary
#2348
Checklist
docs/build.mddocs/ctest.mdbase...HEADcontract matrix for cross-cutting changes:producer, consumer, build/runtime behavior, platform/toolchain boundary,
CI trigger, dependency owner, and local evidence
docs/python-packaging-release.mdfor PR and merge requirementsm_membersLegal Requirements
All official software projects hosted by the International Color Consortium (ICC)
follows the open source software best practice policies. The International Color Consortium IP policy governs ICC specification development and contributions to ICC open source software. Software contributions are also covered by the Contributor License Agreement (CLA).
Contributor License Agreements
Developers who wish to contribute code to be considered for inclusion
in ICC software must first complete a Contributor License Agreement
(CLA).
There is no cost or membership requirement to sign the ICC Contributor License Agreement (CLA). Please note that this is different from membership in the International Color Consortium (ICC). If your organization relies on our projects, please become a member. Membership dues are an essential source of funding and investment for these projects.
If you are an individual writing the code on your own time and you are SURE you are the sole owner of any intellectual property you contribute, you can sign the CLA as an individual contributor.
If you are writing the code as part of your job, or if there is any possibility that your employer might think they own any intellectual property you create, then you should use the Corporate Contributor Licence Agreement
License
ICC software is licensed under the BSD 3-Clause "New" or "Revised" License. Contributions to ICC software projects should abide by that license unless otherwised specified or approved by the ICC.
Copyright Notices
All new source files must begin with the ICC Copyright notice and include or reference the BSD 3-Clause "New" or "Revised" License.
INTELLECTUAL PROPERTY & PATENTS
Participation in ICC's development activities is subject to ICC's Patent Policy.
Maintainer Review Required
If you have questions, contact a listed Maintainer.