Skip to content

Latest commit

 

History

History
53 lines (43 loc) · 5.83 KB

File metadata and controls

53 lines (43 loc) · 5.83 KB

● Based on the analysis, here's what the situation looks like and where there's room to improve:

Already parallelized:
The guided and shelf passes both fan out across threads, dividing width candidates into ranges. So the outer search loop is already threaded.

The real bottlenecks:

  1. Static range division → load imbalance
    The current pattern divides [0, width_candidates.size()) evenly across threads. But different widths take different amounts of time (a narrow atlas packs quickly; a near-square one generates many free-rect fragments and runs longer). A slow thread stalls the join. Replacing the static split with an atomic work index (each thread atomically claims the next unclaimed width) would eliminate this with minimal code change.

  2. Two separate thread fan-out/join cycles Guided and shelf are sequentially phased — spawn threads, join, spawn again, join. Merging them into one pass (each worker does both MaxRects and shelf for its assigned widths) halves the thread lifecycle overhead and improves cache locality since the same width's sprite list is touched once.

  3. O(n²) free-rect pruning inside pack_compact_maxrects Every time a sprite is placed, prune_free_rects does an O(n²) containment scan to discard dominated rectangles. For large sprite sets the free-rect list can grow significantly. This is the core per-packing cost and runs entirely on a single thread — no parallelism is possible inside it without a fundamentally different data structure (e.g. a spatial index). This is the hardest win but potentially the biggest one.

  4. No pruning across (sort × heuristic) combos within a width For a given width, all 12 combinations (4 sorts × 3 heuristics) are always attempted. If the first combo already produces a result that can't beat the global best, the remaining 11 could be skipped. The candidate-level pruning exists before entering the width, but not within it.

  5. Candidate count ~20–60 candidates × 16 packing attempts each. The offset step grid ({0, ±1, ±2, ±4, ±8, ±12, ±16, ±20} × 4 anchors) is generous. With a warm seed cache the search could be narrowed to a smaller window around the known-good width rather than the full grid.

Summary by effort vs. impact:

┌────────────────────────────────────────────────────┬────────┬───────────────────────────────────────┐ │ Change │ Effort │ Impact │ ├────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┤ │ Atomic work index (dynamic scheduling) │ Low │ Medium — eliminates load imbalance │ ├────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┤ │ Merge guided + shelf into one pass │ Low │ Small — saves two thread cycles │ ├────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┤ │ Per-width early exit across (sort × heuristic) │ Medium │ Small-medium │ ├────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┤ │ Narrower candidate window when seed is warm │ Medium │ Medium — fewer packing runs │ ├────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┤ │ Replace O(n²) free-rect pruning with spatial index │ High │ Potentially large for big sprite sets │ └────────────────────────────────────────────────────┴────────┴───────────────────────────────────────┘

The atomic work index is the most straightforward improvement given the existing code structure — want me to implement it?