Optimized solver queue and trail profile — 20 August 2026
This report measures wang_solve_optimized() at commit 535012b8, after the
byte-wise support table was introduced. The profiled revision adds diagnostic
counters only. It does not deduplicate the queue, index MRV, compact the trail,
add TaskPlan, or enable OpenMP. The reference and optimized paths retain their
existing propagation,
search, rollback, and witness semantics.
Reproduction identity
The immutable baseline is Git commit:
535012b8ca6927948014047037896e713e9e38a9
Add byte-wise support lookup to optimized solver
A detached temporary worktree at that commit produced the portable -O2
schema-v5 benchmark binary. Its identities were:
ecb6e558af0bb11009a9a38a4e2269ccd70b03948daad9014c9a095c49ecdb5e build/benchmarks/c/bench_solver
89ac064039f4b1660b844b092bbc972935e53aefddfb5a5f391023fae27c046d include/wang/solver.h
4ea9ba456a1342165b3169823bf8a2ab21f0b942d71583c1acb6af34c215921d src/solver/solver_serial.c
15278b0b479a7d00d8eaafabb5cb3bed52037252f5e842b331f2fa750e9b48fa benchmarks/c/bench_solver.c
Benchmark schema v6 adds only the five counters defined below. Source identities for the instrumented revision are:
c87b6b90cd09632f1ffb730f10399a65ac5c89f5a6942ef87d53b82374bfe8f7 include/wang/solver.h
b56521eea0a0230dbe108a5e0640e3fcc852986cfaa70650d97a6a9f41e1df6b src/solver/solver_serial.c
39ff014ca1e16e86b40bc425a222e241d86020e434d8b921213e761a57377909 benchmarks/c/bench_solver.c
f12ac16d8e0e667aa219d94e53a4a5c3e5d98472c0a46b58e7b238ba1ff698a0 tests/c/test_solver.c
07b0dcd2ac2bd2dbb560809a154d47b89a929a96895ff672402d15f950a9b5e1 tests/c/test_solver_differential.c
Environment:
Debian GNU/Linux 13
Linux 6.12.101+deb13-amd64 x86_64
AMD Ryzen 5 3600, 6 cores / 12 threads, boost enabled
GCC 14.2.0
Clang 19.1.7
C17, -O2 portable build; no -march=native or LTO
Valgrind/Callgrind/Cachegrind 3.24.0
Native timings were pinned to CPU 2. Frequency was not pinned and the host was not isolated, so medians are descriptive host evidence rather than confidence intervals. Metric runs are separate single executions and are not timing samples.
Preventive red test
The public metric expectations were added to tests/c/test_solver.c before
the implementation. The preventive command was:
make build/tests/c/test_solver
It failed with exit status 2 because WangSolverMetrics had no members named
enqueue_attempts, duplicate_enqueue_attempts, queue_unique_peak,
initial_trail_rewrites, or search_trail_rewrites. After implementation,
the same fixture fixes exact expected values for all five fields and checks
the general bounds duplicates <= attempts, unique_peak <= queue_peak, and
rewrites <= writes.
Counter definitions
Counters are collected only with WANG_SOLVE_COLLECT_METRICS; otherwise every
field remains zero.
enqueue_attemptscounts every request to append a cell index to the FIFO, including initial active cells, branch seeds, and propagation-induced requests.duplicate_enqueue_attemptscounts an enqueue request when that cell already has at least one unconsumed occurrence in the FIFO. The operation is still performed: this profile observes duplicates and does not remove them.queue_unique_peakis the maximum number of distinct cell indices among unconsumed FIFO entries. Existingqueue_peakremains the maximum total number of unconsumed occurrences.initial_trail_rewritescounts a reference-path trail entry after the first entry for the same cell during the single initial-propagation interval. The optimized path records no initial trail and therefore reports zero.search_trail_rewritescounts a trail entry after the first entry for the same cell since the most recent DFS candidate marker. The interval includes the candidate’s singleton restriction and its propagation. A new candidate attempt starts a new interval, including at a deeper DFS frame.
Queue pending multiplicity and trail intervals are metrics-only derived state. Pending state is decremented on every pop and explicitly drained when propagation abandons a queue after conflict or error. It never suppresses an enqueue or changes rollback state.
Method
The detached baseline used five metrics-disabled passes in fresh benchmark processes for each solver/case combination. The current schema-v6 corpus used one metrics-enabled process per solver/case; queue and semantic-work counters were identical. Path-specific counters retain their documented differences in support, initial trail, stack capacity, SAT copy, and table storage. Default and opt-in snapshot modes were repeated for the root UNSAT control.
Callgrind and Cachegrind used one metrics-disabled optimized process per case. Their totals include fixture setup, solving, mandatory witness verification, and teardown, whereas the native solver-only timer begins after fixture construction. Percentages therefore use the stricter whole-process denominator.
Metrics-disabled commit baseline
Milliseconds per solve, five passes, portable -O2, CPU 2:
| Case | Reference median | Optimized median | Optimized range |
|---|---|---|---|
| generic forced thin SAT | 4.767 | 3.150 | 3.097–3.225 |
| generic unconstrained SAT | 181.961 | 144.483 | 142.929–150.850 |
| generic backtracking SAT | 0.033 | 0.014 | 0.014–0.018 |
| generic root UNSAT | 19.213 | 19.083 | 17.985–19.468 |
| Yang–Zhang SAT, 6 variables | 12.464 | 2.503 | 2.472–3.074 |
| Yang–Zhang UNSAT, 6 variables | 3.106 | 0.548 | 0.543–0.722 |
| Yang–Zhang SAT, 12 variables | 98.569 | 20.825 | 20.565–22.785 |
| Yang–Zhang UNSAT, 12 variables | 25.112 | 4.264 | 4.183–5.093 |
The 20.825-ms large-SAT median is within 0.6 percent of the preceding byte-support profile’s 20.701-ms measurement. The passes were not interleaved with that older run, so this is a reproducibility check, not a new optimization comparison.
Direct queue evidence
Queue counters are identical in reference and optimized because the profiled revision does not change queue behavior.
| Case | Enqueue attempts | Pending duplicates | Duplicate share | Queue peak | Unique peak | Peak amplification |
|---|---|---|---|---|---|---|
| generic forced thin SAT | 98,301 | 65,532 | 66.67% | 65,533 | 32,768 | 2.000x |
| generic unconstrained SAT | 126,314 | 36,289 | 28.73% | 27,265 | 9,216 | 2.958x |
| generic backtracking SAT | 138 | 43 | 31.16% | 34 | 16 | 2.125x |
| generic root UNSAT | 0 | 0 | n/a | 0 | 0 | n/a |
| Yang–Zhang SAT, 6 variables | 77,013 | 52,905 | 68.70% | 27,791 | 9,345 | 2.974x |
| Yang–Zhang UNSAT, 6 variables | 19,832 | 14,394 | 72.58% | 7,561 | 2,560 | 2.954x |
| Yang–Zhang SAT, 12 variables | 645,444 | 445,545 | 69.03% | 228,255 | 76,247 | 2.994x |
| Yang–Zhang UNSAT, 12 variables | 159,813 | 118,571 | 74.19% | 60,715 | 20,317 | 2.988x |
The Yang–Zhang propagation cases consistently attempt about three queue occurrences at peak for each distinct pending cell, and 68.70–74.19 percent of all enqueue requests are already pending. This is direct evidence of redundant queue work, not merely a large allocation watermark.
Direct trail evidence
The table shows the reference initial interval because that path still records it, and the optimized search trail because it is the only rollback trail that an optimized-path compaction could improve. Reference and optimized search counts are identical.
| Case | Reference initial writes | Initial rewrites | Initial share | Optimized search writes | Search rewrites | Search share |
|---|---|---|---|---|---|---|
| generic forced thin SAT | 65,533 | 32,766 | 50.00% | 0 | 0 | n/a |
| generic unconstrained SAT | 36,290 | 27,074 | 74.61% | 80,808 | 8,930 | 11.05% |
| generic backtracking SAT | 46 | 31 | 67.39% | 78 | 13 | 16.67% |
| generic root UNSAT | 0 | 0 | n/a | 0 | 0 | n/a |
| Yang–Zhang SAT, 6 variables | 60,290 | 50,959 | 84.52% | 7,378 | 12 | 0.163% |
| Yang–Zhang UNSAT, 6 variables | 16,265 | 13,719 | 84.35% | 1,009 | 6 | 0.595% |
| Yang–Zhang SAT, 12 variables | 510,665 | 434,444 | 85.07% | 58,532 | 24 | 0.041% |
| Yang–Zhang UNSAT, 12 variables | 135,600 | 115,309 | 85.04% | 3,898 | 6 | 0.154% |
The apparently large initial rewrite opportunity is not present in the optimized path because it already omits its entire non-rollbackable initial trail. On the optimized Yang–Zhang search trail, repeated writes between markers are only 0.041–0.595 percent. Generic backtracking and unconstrained search reach 16.67 and 11.05 percent respectively, but neither outweighs the cross-corpus queue evidence or the unconstrained MRV hotspot below.
Diagnostic modes
Default and WANG_SOLVE_CAPTURE_UNSAT_SNAPSHOT runs of the 2,097,152-cell root
UNSAT case returned the same status, conflict cell, and zero queue/trail work
for both solvers. Snapshot mode alone returned the requested dense domains.
The C suite also exercised capped failed-leaf tracing on the backtracking
fixture while fixing the exact queue/trail counters, which covers reset after
conflicts and subsequent candidate attempts.
Callgrind attribution
One metrics-disabled optimized execution per row:
| Case | Whole-process instructions | Dominant self function | Self share | Solver inclusive |
|---|---|---|---|---|
| generic forced thin SAT | 61,673,219 | propagate_queue() |
25.61% | not isolated |
| generic unconstrained SAT | 1,965,873,358 | select_mrv_cell() |
96.90% | 99.96% |
| Yang–Zhang SAT, 6 variables | 40,393,824 | propagate_queue() |
55.74% | not isolated |
| Yang–Zhang SAT, 12 variables | 336,041,260 | propagate_queue() |
56.35% | 93.14% |
The byte-support mechanism removed much of the support-union work, reducing the propagation share from the earlier reference profile’s approximately 84 percent to about 56 percent on Yang–Zhang SAT. Propagation remains the largest named self hotspot. The unconstrained regime is different: its unchanged row-major MRV scan alone executes 1,905,013,160 instructions.
Cachegrind attribution
One metrics-disabled optimized execution per row, with cache and branch simulation enabled:
| Case | Data references | D1 miss rate | LLd miss rate | Branch mispredict rate |
|---|---|---|---|---|
| generic unconstrained SAT | 190,207,663 | 3.0% | effectively 0.0% | 3.3% |
| Yang–Zhang SAT, 6 variables | 9,295,136 | 0.5% | 0.2% | 3.7% |
| Yang–Zhang SAT, 12 variables | 75,543,537 | 0.5% | 0.1% | 3.4% |
As in the preceding byte-support profile, the dominant cost is instruction/work volume rather than a high last-level data miss rate. Cachegrind is a deterministic cache model, not a native hardware-counter measurement.
Decision
The profile selects queue deduplication as the next isolated mechanism. It has direct, substantial evidence across forced propagation and every medium/large Yang–Zhang case: 66.67–74.19 percent duplicate enqueue requests and peak FIFO amplification close to 3x. Evaluation requires propagation correctness, deterministic solver contracts, direct index-memory measurement, and a full corpus comparison. Duplicate counts alone do not imply an equal timing gain.
An MRV index remains the next distinct candidate for weakly constrained deep search. The unconstrained case spends 96.90 percent of whole-process instructions in linear MRV and scans 43,897,478 cells, while Yang–Zhang uses only 1–241,688 scans. It should not displace the broader queue mechanism or be combined with it.
Trail compaction is not justified now. The optimized path already omits the only high-rewrite interval, and its Yang–Zhang search rewrite shares are below 0.6 percent. The larger generic shares can be reconsidered only if a later profile shows trail operations dominating after queue and MRV work.
Limitations
- Metrics-only pending and interval arrays add diagnostic memory and work; timing and profiler runs therefore disable metrics.
- A pending duplicate counter establishes redundant scheduling, not the cost
of an
in_queuetest or the net effect on propagation locality. queue_unique_peakmeasures simultaneous distinct pending cells, not the minimum capacity of a future ring-buffer implementation.- Trail rewrite counters identify entries removable within the stated marker intervals; they do not model every possible trail representation.
- Native frequency was not fixed, profiling used one simulated execution per case, and process totals include harness work.
- No conclusion about
TaskPlanor OpenMP follows from these serial profiles.
Reproduction commands
Baseline worktree and native timing:
git worktree add --detach /tmp/tiling-foundry-queue-profile-baseline \
535012b8ca6927948014047037896e713e9e38a9
make -C /tmp/tiling-foundry-queue-profile-baseline build/benchmarks/c/bench_solver
cd /tmp/tiling-foundry-queue-profile-baseline
taskset -c 2 build/benchmarks/c/bench_solver \
--case yang_zhang_sat_large_solver --solver optimized
Direct metrics and diagnostic control:
build/benchmarks/c/bench_solver \
--case yang_zhang_sat_large_solver --solver optimized \
--iterations 1 --metrics
build/benchmarks/c/bench_solver \
--case generic_root_unsat --solver optimized \
--iterations 1 --metrics --capture-unsat
Profilers:
valgrind --tool=callgrind --error-exitcode=1 \
--callgrind-out-file=/tmp/queue-profile-callgrind.out \
build/benchmarks/c/bench_solver \
--case yang_zhang_sat_large_solver --solver optimized --iterations 1
callgrind_annotate --inclusive=yes /tmp/queue-profile-callgrind.out
valgrind --tool=cachegrind --cache-sim=yes --branch-sim=yes \
--error-exitcode=1 --cachegrind-out-file=/tmp/queue-profile-cachegrind.out \
build/benchmarks/c/bench_solver \
--case yang_zhang_sat_large_solver --solver optimized --iterations 1
Verification status
The final implementation passed make check, strict GCC and Clang builds,
ASan/UBSan/LeakSanitizer, GCC static analysis, the complete Memcheck target,
and the complete Cachegrind target. The first sanitizer attempt stopped at
LeakSanitizer’s known ptrace limitation; the same target completed outside the
ptrace sandbox without a finding. Targeted metrics-disabled Callgrind and
Cachegrind executions also completed for the optimized profile cases.
Build, profiler, and temporary baseline artifacts were removed after recording the evidence.