Skip to main content

SingleFast

Move Type: Basic Complexity: O(objects × containers) with early termination

Move one object at a time but stop early after finding an improvement. Faster than Single but may miss better moves.

Overview

SingleFast evaluates moving each object from the hot container to every possible destination container, but uses early termination to improve performance. After fully exploring minHotObjects (default: 1), it returns the best improving move found so far.

Use when:

  • Speed is more important than finding the absolute best move
  • Want faster convergence than Single
  • Willing to accept local optima for performance

Avoid when:

  • Need thorough exploration of all moves
  • Problem is small enough for complete Single search
  • Quality is more important than speed

Quick Example

# Use SingleFast for faster convergence (stops after finding improvement)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFastMoveTypeSpec(), # Fast early-exit single moves
]
)
)
)

Parameters

ParameterTypeRequiredDefaultDescription
destinationsToExploreDestinationsToExploreOptionsNonullRestrict which containers to explore
minHotObjectsint32No1Minimum objects to fully explore before returning

How It Works

Given a hot container (the container contributing most to the objective):

  1. Select object: Pick first object from the hot container
  2. Evaluate all destinations: Test moving this object to every possible destination container (in parallel)
  3. Check improvement: If any move improves the objective, remember the best one
  4. Early exit check: If we've explored minHotObjects and found an improving move, stop and return it
  5. Continue if needed: If no improvement found, move to next object
  6. Repeat: Until improvement found or all objects exhausted

Visual Example

Iteration 1 - Explore obj1:
┌─────────────┐ Test all destinations (parallel)
│ Hot │ ──> dest1: +5 improvement ✓
│ Container │ ──> dest2: -2 worse
│ • obj1 ← │ ──> dest3: +3 improvement
│ • obj2 │
│ • obj3 │ Best: obj1→dest1 (+5)
└─────────────┘
minHotObjects=1 reached → STOP, return obj1→dest1

Without early exit (Single), would also explore:
obj2 → all dests
obj3 → all dests

Difference from Single

AspectSingleSingleFast
ExplorationAll objects, all destinationsStops after minHotObjects if improvement found
ParallelizationAll moves in parallelPer-object destinations in parallel
GuaranteeFinds best single moveFinds early improving move
SpeedSlowerFaster

Complexity

Minimum moves evaluated: O(minHotObjects × C) Maximum moves evaluated: O(N × C) (same as Single if no improvements found)

Where:

  • N = number of objects in hot container
  • C = number of destination containers
  • minHotObjects = parameter (default: 1)

Example - Best case (improvement found early):

  • Hot container has 1000 objects
  • System has 100 containers
  • minHotObjects = 1 (default)
  • Moves evaluated = 1 × 100 = 100 moves (vs 100,000 for Single)

Example - Worst case (no improvements):

  • Moves evaluated = 1000 × 100 = 100,000 moves (same as Single)

Usage Patterns

Basic Fast Configuration

Default usage for faster convergence:

# SingleFast is good default for faster convergence
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFastMoveTypeSpec(), # Use fast variant by default
]
)
)
)

Combined with Single

Try SingleFast first, fall back to Single:

# Try fast moves first, then thorough search if needed
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFastMoveTypeSpec(), # Try fast moves first
SingleMoveTypeSpec(), # Fall back to thorough search
]
)
)
)

Explore More Objects Before Stopping

Increase minHotObjects for better quality:

# Explore 5 objects before stopping (better quality)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFastMoveTypeSpec(
minHotObjects=5, # Explore 5 objects before early exit
),
]
)
)
)

Restrict Destinations

Limit search space for even faster convergence:

# Only explore moves within the same rack
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFastMoveTypeSpec(
destinationsToExplore=MoveToCurrentScopeItemSpec(
scopeNameForExploringMovesToCurrentScopeItem="rack",
),
),
]
)
)
)

Interactive Applications

Fast responses for UI/dashboards:

# Fast solver for UI/dashboard (1 second limit)
solver.addGoal(
GoalSpecs(
balanceSpec=BalanceSpec(name="balance-cpu", scope="host", dimension="cpu")
),
weight=1.0,
)

solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
solveTime=1, # 1 second limit for interactive response
moveTypeList=[
SingleFastMoveTypeSpec(), # Fast moves only
],
)
)
)

Performance Characteristics

Scalability

Problem SizeObjects/ContainerContainersSingleFast (typical)Single (always)
Small1010<1ms<1ms
Medium1001001-5ms10-50ms
Large1,0001,00010-100ms0.5-2s
Very Large10,00010,000100ms-2s10-60s

Note: Times vary based on when improvements are found. Best case is 10-100x faster than Single.

Speedup Factor

Actual speedup depends on problem characteristics:

  • Best case (improvements found early): 10-100x faster
  • Average case (improvements found midway): 2-10x faster
  • Worst case (no improvements): Same as Single

Multi-threading

  • Per-object destinations evaluated in parallel
  • Scales well up to 8-16 cores
  • Less parallel work than Single (explores fewer objects)

Comparison with Variants

Move TypeSpeedThoroughnessQualityUse Case
SingleFastFastEarly exitGoodDefault fast choice
SingleMediumCompleteBestNeed best single move
SingleGreedyFastestGreedyFairSpeed critical, single-threaded OK
SingleRandomBatchesFastBatchedGoodParallel batching preferred

Recommendation:

  • Use SingleFast as default for faster convergence
  • Use Single when quality is critical and time is available
  • Use SingleGreedy for single-threaded or extremely time-sensitive cases

Troubleshooting

Problem: SingleFast not much faster than Single

Diagnosis: Few or no improvements being found early

Solutions:

  • This is expected for highly constrained or broken initial assignments
  • Try improving initial assignment quality
  • Consider using SingleGreedy if speed is critical
  • Check if problem has many feasible moves

Problem: Solution quality worse than Single

Diagnosis: Early termination missing better moves

Solutions:

  • Increase minHotObjects to explore more before stopping
  • Use SingleFast for initial passes, switch to Single for final refinement
  • Combine both: [SingleFastMoveTypeSpec(), SingleMoveTypeSpec()]
  • Accept the trade-off (speed vs quality)

Problem: Still too slow

Diagnosis: Worst-case behavior (no early improvements)

Solutions:

  • Reduce destinations with destinationsToExplore
  • Use SingleGreedy for single-threaded speed
  • Use SingleRandomBatches for parallel batching
  • Check if initial assignment allows any improvements

Problem: Getting stuck in local optima

Diagnosis: Early termination prevents finding escape moves

Solutions:

  • Add more powerful move types after SingleFast (Swap, Chain)
  • Increase minHotObjects to be less greedy
  • Use Single periodically for thorough search
  • Try multiple solver runs with different seeds

Variants:

Complementary:

When to use which:

  • Small problem (<1K objects): Use Single (thoroughness worth the time)
  • Medium problem (1-10K objects): Use SingleFast (good speed/quality balance)
  • Large problem (>10K objects): Use SingleFast or SingleGreedy
  • Interactive/UI: Use SingleFast with low time limits

Source Code

Next Steps