@@ -301,3 +301,149 @@ total physical length differed by 2.26%. Automatic TEASAR execution therefore
301301remains FP64. Parallel shortest paths remain a separate follow-up.
302302
303303Final verification after selecting compact on-the-fly FP64: ` 1129 passed ` .
304+
305+ ## Implemented parallel follow-up
306+
307+ The shared thread budget for the distance transform and compact Dijkstra solves
308+ was implemented and measured on 2026-07-15. The reference matrix now passes
309+ the same worker counts to kimimaro through ` parallel=N ` . Reproduce it with:
310+
311+ ``` bash
312+ python development/skeleton/benchmark_teasar.py \
313+ --large --kimimaro --threads 1 2 4 8 \
314+ --repeats 3 --warmup 1 \
315+ --json /tmp/teasar_large_parallel.json
316+ ```
317+
318+ The environment was the same Intel Core i7-1185G7 host (4 cores / 8 hardware
319+ threads), with kimimaro 5.8.1 and EDT 3.1.1. Each backend received one warmup;
320+ backend order was shuffled deterministically for each of the three measured
321+ calls. The table reports the median with the minimum in parentheses. Scaling
322+ is relative to the corresponding single-worker median, and ` bio / kimi ` is the
323+ matched median-time ratio (lower favors bioimage-cpp).
324+
325+ | volume | workers | bioimage-cpp median (min) | bio scaling | kimimaro median (min) | kimi scaling | bio / kimi |
326+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
327+ | ` 128^3 ` | 1 | 77.60 ms (73.83 ms) | 1.00x | 110.43 ms (103.32 ms) | 1.00x | 0.70x |
328+ | ` 128^3 ` | 2 | 60.42 ms (56.16 ms) | 1.28x | 143.28 ms (136.60 ms) | 0.77x | 0.42x |
329+ | ` 128^3 ` | 4 | 50.01 ms (43.15 ms) | 1.55x | 152.51 ms (149.16 ms) | 0.72x | 0.33x |
330+ | ` 128^3 ` | 8 | 55.71 ms (45.21 ms) | 1.39x | 172.32 ms (171.98 ms) | 0.64x | 0.32x |
331+ | ` 192^3 ` | 1 | 303.83 ms (283.12 ms) | 1.00x | 381.02 ms (364.20 ms) | 1.00x | 0.80x |
332+ | ` 192^3 ` | 2 | 235.93 ms (216.57 ms) | 1.29x | 393.61 ms (389.73 ms) | 0.97x | 0.60x |
333+ | ` 192^3 ` | 4 | 209.54 ms (205.03 ms) | 1.45x | 413.88 ms (409.37 ms) | 0.92x | 0.51x |
334+ | ` 192^3 ` | 8 | 195.08 ms (188.79 ms) | 1.56x | 431.37 ms (417.77 ms) | 0.88x | 0.45x |
335+ | ` 256^3 ` | 1 | 694.69 ms (681.63 ms) | 1.00x | 931.81 ms (883.13 ms) | 1.00x | 0.75x |
336+ | ` 256^3 ` | 2 | 510.91 ms (509.73 ms) | 1.36x | 950.05 ms (933.76 ms) | 0.98x | 0.54x |
337+ | ` 256^3 ` | 4 | 444.05 ms (424.72 ms) | 1.56x | 902.87 ms (902.36 ms) | 1.03x | 0.49x |
338+ | ` 256^3 ` | 8 | 434.67 ms (424.57 ms) | 1.60x | 938.22 ms (911.45 ms) | 0.99x | 0.46x |
339+
340+ Bioimage-cpp improved through four workers on every tier and was fastest at
341+ eight workers on ` 192^3 ` and ` 256^3 ` . Its best measured scaling was 1.60x on
342+ ` 256^3 ` ; at four workers it was approximately 3.0x, 2.0x, and 2.0x faster than
343+ kimimaro on the three volumes.
344+
345+ Kimimaro's ` parallel ` path is relevant but not fully exercised by this fixture.
346+ It threads the full-volume EDT and then uses a process pool to dispatch
347+ connected components. Each branching tube contains only one component, so the
348+ pool has one useful tracing task and additional workers mostly add process and
349+ shared-memory overhead. The only measured kimimaro improvement was about 3%
350+ at four workers on ` 256^3 ` .
351+
352+ Worker count did not change either implementation's graph size:
353+
354+ | volume | bioimage-cpp vertices / edges | kimimaro vertices / edges |
355+ | --- | ---: | ---: |
356+ | ` 128^3 ` | 213 / 212 | 213 / 212 |
357+ | ` 192^3 ` | 340 / 339 | 323 / 322 |
358+ | ` 256^3 ` | 415 / 414 | 443 / 442 |
359+
360+ Verification after the benchmark update: ` 24 passed ` in
361+ ` tests/skeleton/test_teasar.py ` ; the matched-matrix and sequential-backend
362+ smoke runs also completed successfully.
363+
364+ ## Implemented non-Dijkstra optimization follow-up
365+
366+ Profiling after the compact and parallel-Dijkstra work showed that further
367+ Dijkstra changes were no longer the best target. On the ` 256^3 ` branching tube
368+ at four workers, the two root fields plus all rail paths took about 85 ms, while
369+ the full-volume EDT alone took about 219 ms. The following exact-semantics
370+ changes were implemented on 2026-07-15:
371+
372+ - the compact TEASAR backends crop to the tight foreground bounding box plus a
373+ one-voxel zero halo and restore the integer crop origin before converting
374+ vertices to physical coordinates;
375+ - DBF values are gathered into compact-node order and the dense DBF is released
376+ before the root/path solves; the padded mask is moved into active state rather
377+ than copied;
378+ - the shared EDT initializes its uninitialized squared-distance buffer during
379+ the first-axis gather and parallelizes distance materialization;
380+ - invalidation maintains a persistent union of x intervals for each ` (z, y) `
381+ row, so overlapping cubes scan only newly covered spans.
382+
383+ The dense backend remains unchanged as a correctness oracle. Compact FP64
384+ vertices, edges, and radii remained array-exact with dense FP64 across embedded,
385+ boundary-touching, sparse, dense, isotropic, anisotropic, and low/high-PDRF
386+ cases.
387+
388+ ### Profile and memory result
389+
390+ One profile-build call on the ` 256^3 ` branching tube at four workers:
391+
392+ | phase | before | after |
393+ | --- | ---: | ---: |
394+ | input crop / padding | unprofiled full volume | 30.6 ms |
395+ | distance transform | 218.5 ms | 48.6 ms |
396+ | compact domain | 42.0 ms | 14.1 ms |
397+ | DBF compaction | -- | 3.3 ms |
398+ | root Dijkstra | 56.6 ms | 57.1 ms |
399+ | PDRF | 3.8 ms | 3.3 ms |
400+ | target selection | 0.8 ms | 0.8 ms |
401+ | rail-path Dijkstra | 28.7 ms | 26.6 ms |
402+ | invalidation | 11.0 ms | 3.7 ms |
403+ | measured Python call | 446.3 ms | 202.3 ms |
404+ | peak RSS | 266,804 KiB | 120,288 KiB |
405+
406+ The unchanged Dijkstra phases confirm that the gain came from the intended
407+ non-Dijkstra work. Peak RSS fell by 54.9%. On the relatively dense ` 96^3 ` ball,
408+ the row-interval union reduced invalidation from 27.2 ms to 2.7 ms and the full
409+ call from 159.8 ms to 118.1 ms, comfortably clearing the invalidation retention
410+ gate.
411+
412+ ### Normal-build wall-clock result
413+
414+ Five-repeat medians with one warmup, using the same deterministic backend-order
415+ shuffle as the earlier matrix:
416+
417+ | volume | workers | optimized median | previous median | speedup |
418+ | --- | ---: | ---: | ---: | ---: |
419+ | ` 128^3 ` | 1 | 36.71 ms | 77.60 ms | 2.11x |
420+ | ` 128^3 ` | 2 | 29.06 ms | 60.42 ms | 2.08x |
421+ | ` 128^3 ` | 4 | 24.17 ms | 50.01 ms | 2.07x |
422+ | ` 128^3 ` | 8 | 24.09 ms | 55.71 ms | 2.31x |
423+ | ` 192^3 ` | 1 | 124.50 ms | 303.83 ms | 2.44x |
424+ | ` 192^3 ` | 2 | 94.47 ms | 235.93 ms | 2.50x |
425+ | ` 192^3 ` | 4 | 85.25 ms | 209.54 ms | 2.46x |
426+ | ` 192^3 ` | 8 | 78.89 ms | 195.08 ms | 2.47x |
427+ | ` 256^3 ` | 1 | 338.64 ms | 694.69 ms | 2.05x |
428+ | ` 256^3 ` | 2 | 241.10 ms | 510.91 ms | 2.12x |
429+ | ` 256^3 ` | 4 | 207.33 ms | 444.05 ms | 2.14x |
430+ | ` 256^3 ` | 8 | 203.90 ms | 434.67 ms | 2.13x |
431+
432+ The extended sequential matrix also retained exact dense parity. Its production
433+ compact medians were 65.67 ms for the sparse embedded ` 192^3 ` tube, 118.03 ms
434+ for the relatively dense ` 96^3 ` ball, and 35.61--38.84 ms across the ` 128^3 `
435+ spacing/PDRF cases.
436+
437+ A fresh three-repeat matched reference run put bioimage-cpp at 0.19x, 0.21x,
438+ and 0.20x kimimaro's four-worker time on ` 128^3 ` , ` 192^3 ` , and ` 256^3 `
439+ respectively (28.75 vs 152.76 ms, 81.64 vs 386.13 ms, and 203.82 vs
440+ 1002.83 ms). Kimimaro still has only one useful component-tracing task on these
441+ single-component fixtures.
442+
443+ The shared EDT change was checked separately on a deterministic 50%-foreground
444+ ` 64 x 512 x 512 ` volume: five-call medians were 1.028 s at one worker and
445+ 344.33 ms at four workers, with exact threaded distances, indices, vectors,
446+ and preallocated outputs.
447+
448+ Final verification: ` 1141 passed ` with third-party pytest plugin autoload
449+ disabled. The normal editable build was restored after profiling.
0 commit comments