Swap
Move Type: Swap Complexity: O(objects²)
Exchange two objects between containers. Essential for capacity-constrained problems where you can't simply add objects without removing others.
Overview
Swap evaluates exchanging pairs of objects between the hot container and other containers. This is critical when containers are near capacity and single moves would violate constraints.
Use when:
- Capacity constraints are tight
- Moving an object requires making room first
- Single moves alone aren't finding improvements
Avoid when:
- Problem is unconstrained (Single is faster and sufficient)
- Problem is very large (>10K objects - use SwapSampled instead)
Quick Example
- Python
- C++
# Use Swap for capacity-constrained problems
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleMoveTypeSpec(), # Try single moves first
SwapMoveTypeSpec(), # Then swaps for capacity constraints
]
)
)
)
void quickExample() {
// Use Swap for capacity-constrained problems
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
// Add Swap
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = SwapMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
partitionNameToExploreSwapsWithinObjectGroup | string | No | null | Only swap objects within same group |
greedyOnSrc | bool | No | false | Exit early when swapping source objects |
greedyOnDst | bool | No | false | Exit early when swapping destination objects |
destinationsToExplore | DestinationsToExploreOptions | No | null | Restrict destination containers |
sampleSize | SampleSize | No | null | Sample subset of objects on both sides |
swapRatioDimension | StringKeyValueMap | No | null | Dimension for uneven k:1 swaps |
objectBundleFormationHints | ObjectBundleFormationHints | No | null | Bundle objects for certain containers |
How It Works
Given a hot container (the container contributing most to the objective):
- Select hot object: Pick one object from the hot container
- Select destination container: Pick a different container
- Select other object: Pick one object from the destination container
- Evaluate: Test swapping the two objects (hot object ↔ other object)
- Repeat: Try all combinations of (hot object, destination container, other object)
- Apply best: Apply the swap that improves the objective most
Visual Example
Before: After (if swap applied):
┌─────────────┐ ┌─────────────┐
│ Hot │ │ Hot │
│ Container │ ┌─swap─┐ │ Container │
│ • obj1 │ ───┤ │──> │ • objX ←─┐ │
│ • obj2 │ │ │ │ • obj2 │ │
│ • obj3 │ ←──┤ │─── │ • obj3 │ │
└─────────────┘ └──────┘ └───────────┼─┘
┌─────────────┐ ┌───────────┼─┐
│ Other │ │ Other ↓ │
│ Container │ │ Container │
│ • objX │ │ • obj1 ← │
│ • objY │ │ • objY │
└─────────────┘ └─────────────┘
Complexity
Moves evaluated per iteration: O(N × M × C)
- Simplifies to O(N²) when containers have similar sizes
Where:
- N = number of objects in hot container
- M = average number of objects in destination containers
- C = number of destination containers
Example:
- Hot container has 100 objects
- Each destination container has ~100 objects
- System has 50 containers
- Moves evaluated = 100 × 100 × 50 = 500,000 moves
Warning: Swap is much more expensive than Single. Use judiciously.
Usage Patterns
Basic Capacity-Constrained Problem
Most common usage - combine Single with Swap:
- Python
- C++
# Capacity is tight - need swaps to make progress
solver.addConstraint(
ConstraintSpecs(
capacitySpec=CapacitySpec(
name="memory-capacity",
scope="host",
dimension="memory",
limit=Limit(globalLimit=64.0),
)
)
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleMoveTypeSpec(), # Single moves when there's room
SwapMoveTypeSpec(), # Swaps when capacity is tight
]
)
)
)
void basicCapacityConstrained() {
// Capacity is tight - need swaps to make progress
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
CapacitySpec capacitySpec;
capacitySpec.name() = "memory-capacity";
capacitySpec.scope() = "host";
capacitySpec.dimension() = "memory";
Limit limit;
limit.globalLimit() = 64.0;
capacitySpec.limit() = std::move(limit);
solver.addConstraint(std::move(capacitySpec));
LocalSearchSolverSpec solverSpec;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = SwapMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Greedy Swap (Faster)
Use greedy flags for faster convergence:
- Python
- C++
# Exit early when improvement found (faster)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
greedyOnSrc=True, # Try src objects one by one, exit on improvement
greedyOnDst=True, # Try dst objects one by one, exit on improvement
),
]
)
)
)
void greedySwap() {
// Exit early when improvement found (faster)
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
swapSpec.greedyOnSrc() = true;
swapSpec.greedyOnDst() = true;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Sampled Swap (Large Problems)
For large problems, sample objects to reduce cost:
- Python
- C++
# For large problems, sample subset of objects
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=100, # Sample 100 objects on each side
),
),
]
)
)
)
void sampledSwap() {
// For large problems, sample subset of objects
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 100;
swapSpec.sampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Swap Within Groups
Only swap objects within the same partition group:
- Python
- C++
# Only swap objects from the same group (e.g., same database shard)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
partitionNameToExploreSwapsWithinObjectGroup="shard",
),
]
)
)
)
void swapWithinGroups() {
// Only swap objects from the same group (e.g., same database shard)
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
swapSpec.partitionNameToExploreSwapsWithinObjectGroup() = "shard";
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Restricted Destinations
Limit which containers to explore:
- Python
- C++
# Only swap within the same rack (limit destinations)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
destinationsToExplore=MoveToCurrentScopeItemSpec(
scopeNameForExploringMovesToCurrentScopeItem="rack",
),
),
]
)
)
)
void restrictedDestinations() {
// Only swap within the same rack (limit destinations)
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
MoveToCurrentScopeItemSpec destSpec;
destSpec.scopeNameForExploringMovesToCurrentScopeItem() = "rack";
swapSpec.destinationsToExplore().ensure().moveToCurrentScopeItem() = destSpec;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Performance Optimization
When Swap is Slow
Greedy flags: Exit early when improvement found
SwapMoveTypeSpec(
greedyOnSrc=True, # Try src objects one by one, exit on improvement
greedyOnDst=True, # Try dst objects one by one, exit on improvement
)
Sampling: Evaluate subset of swaps
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=100, # Only sample 100 objects per side
),
)
Destination filtering: Limit containers to explore
SwapMoveTypeSpec(
destinationsToExplore=MoveToCurrentScopeItemSpec(
scopeNameForExploringMovesToCurrentScopeItem="rack",
),
)
Recommended Settings by Problem Size
| Objects | Containers | Configuration |
|---|---|---|
| <1K | <100 | Default Swap (no sampling) |
| 1K-10K | 100-1K | Greedy flags + sampling (100-500) |
| >10K | >1K | SwapSampled with aggressive sampling |
Comparison with Variants
| Move Type | Speed | Completeness | Use Case |
|---|---|---|---|
| Swap | Slow | Complete | Default swap, thorough |
| SwapSampled | Medium | Sampled | Large problems |
| SwapFullContainers | Medium | Full containers only | Container-level swaps |
| SwapFullWithEmpty | Fast | Empty targets only | Consolidation |
Troubleshooting
Problem: Swap is too slow
Diagnosis: O(N²) complexity too expensive for problem size
Solutions:
- Enable
greedyOnSrc=TrueandgreedyOnDst=True - Add sampling:
sampleSize=SampleSize(defaultSampleSize=100) - Switch to SwapSampled for large problems
- Restrict destinations with
destinationsToExplore - Use SwapFullWithEmpty if applicable
Problem: Swap not improving objective
Diagnosis: May need more complex moves
Solutions:
- Ensure Single moves tried first (Swap should be after Single)
- Add SingleEndChain for 2-move sequences
- Check if swaps are actually beneficial for your problem
- Verify capacity constraints are preventing Single moves
Problem: Swap causing constraint violations
Diagnosis: Swapped objects have different dimensions
Solutions:
- Check that swapped objects satisfy constraints
- Use
partitionNameToExploreSwapsWithinObjectGroupto swap similar objects - Review capacity and group count constraints
- May need different move type (e.g., SingleEndChain)
Related Move Types
Variants:
- SwapSampled - Sampled version for large problems
- SwapFullContainers - Swap all objects between containers
- SwapFullWithEmpty - Move all to empty container
Alternatives:
- SingleEndChain - 2-move sequence (often better than Swap)
- Single - Simpler, use when capacity not tight
Complementary:
- Single - Always use Single before Swap
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:537 - Implementation:
solver/moves/SwapMoveType.h - Tests:
solver/moves/tests/SwapMoveTypeTest.cpp
Next Steps
- Try SwapSampled for large-scale problems
- Learn about SingleEndChain as alternative to Swap
- Review Move Types Overview for choosing move types
- See Performance Guide for optimization strategies