omnimalloc 0.3.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnimalloc/__init__.py +19 -0
- omnimalloc/_cpp.cp313-win_amd64.pyd +0 -0
- omnimalloc/_cpp.pyi +85 -0
- omnimalloc/allocate.py +49 -0
- omnimalloc/allocators/__init__.py +25 -0
- omnimalloc/allocators/base.py +26 -0
- omnimalloc/allocators/genetic.py +187 -0
- omnimalloc/allocators/greedy.py +82 -0
- omnimalloc/allocators/greedy_cpp.py +65 -0
- omnimalloc/allocators/hillclimb.py +215 -0
- omnimalloc/allocators/minimalloc.py +109 -0
- omnimalloc/allocators/naive.py +23 -0
- omnimalloc/allocators/random.py +52 -0
- omnimalloc/allocators/utils.py +26 -0
- omnimalloc/benchmark/__init__.py +21 -0
- omnimalloc/benchmark/benchmark.py +245 -0
- omnimalloc/benchmark/converters/__init__.py +11 -0
- omnimalloc/benchmark/converters/model.py +164 -0
- omnimalloc/benchmark/converters/onnx.py +142 -0
- omnimalloc/benchmark/results/__init__.py +9 -0
- omnimalloc/benchmark/results/campaign.py +135 -0
- omnimalloc/benchmark/results/export.py +232 -0
- omnimalloc/benchmark/results/report.py +123 -0
- omnimalloc/benchmark/results/result.py +49 -0
- omnimalloc/benchmark/results/utils.py +68 -0
- omnimalloc/benchmark/results/visualize.py +309 -0
- omnimalloc/benchmark/sources/__init__.py +17 -0
- omnimalloc/benchmark/sources/base.py +176 -0
- omnimalloc/benchmark/sources/generator.py +302 -0
- omnimalloc/benchmark/sources/huggingface.py +261 -0
- omnimalloc/benchmark/sources/minimalloc.py +167 -0
- omnimalloc/benchmark/sources/utils.py +26 -0
- omnimalloc/benchmark/timer.py +142 -0
- omnimalloc/benchmark/utils.py +3 -0
- omnimalloc/common/__init__.py +8 -0
- omnimalloc/common/directories.py +13 -0
- omnimalloc/common/optional.py +30 -0
- omnimalloc/common/registry.py +72 -0
- omnimalloc/common/units.py +18 -0
- omnimalloc/primitives/__init__.py +10 -0
- omnimalloc/primitives/allocation.py +11 -0
- omnimalloc/primitives/memory.py +61 -0
- omnimalloc/primitives/pool.py +86 -0
- omnimalloc/primitives/system.py +38 -0
- omnimalloc/primitives/utils.py +39 -0
- omnimalloc/py.typed +1 -0
- omnimalloc/validate.py +96 -0
- omnimalloc/visualize.py +390 -0
- omnimalloc-0.3.0.dist-info/METADATA +272 -0
- omnimalloc-0.3.0.dist-info/RECORD +52 -0
- omnimalloc-0.3.0.dist-info/WHEEL +5 -0
- omnimalloc-0.3.0.dist-info/licenses/LICENSE +201 -0
omnimalloc/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
|
|
7
|
+
__version__ = version("omnimalloc")
|
|
8
|
+
|
|
9
|
+
from .allocate import run_allocation as run_allocation
|
|
10
|
+
from .allocators import get_available_allocators as get_available_allocators
|
|
11
|
+
from .allocators import get_default_allocator as get_default_allocator
|
|
12
|
+
from .primitives import Allocation as Allocation
|
|
13
|
+
from .primitives import BufferKind as BufferKind
|
|
14
|
+
from .primitives import IdType as IdType
|
|
15
|
+
from .primitives import Memory as Memory
|
|
16
|
+
from .primitives import Pool as Pool
|
|
17
|
+
from .primitives import System as System
|
|
18
|
+
from .validate import validate_allocation as validate_allocation
|
|
19
|
+
from .visualize import plot_allocation as plot_allocation
|
|
Binary file
|
omnimalloc/_cpp.pyi
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
import enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BufferKind(enum.Enum):
|
|
6
|
+
def __str__(self) -> str: ...
|
|
7
|
+
|
|
8
|
+
def __repr__(self) -> str: ...
|
|
9
|
+
|
|
10
|
+
WORKSPACE = 0
|
|
11
|
+
|
|
12
|
+
CONSTANT = 1
|
|
13
|
+
|
|
14
|
+
INPUT = 2
|
|
15
|
+
|
|
16
|
+
OUTPUT = 3
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def is_io(self) -> bool: ...
|
|
20
|
+
|
|
21
|
+
def __hash__(self) -> int: ...
|
|
22
|
+
|
|
23
|
+
class Allocation:
|
|
24
|
+
def __init__(self, id: int | str, size: int, start: int, end: int, offset: int | None = None, kind: BufferKind | None = None) -> None: ...
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def id(self) -> int | str: ...
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def size(self) -> int: ...
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def start(self) -> int: ...
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def end(self) -> int: ...
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def offset(self) -> int | None: ...
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def kind(self) -> BufferKind | None: ...
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_allocated(self) -> bool: ...
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def duration(self) -> int: ...
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def height(self) -> int | None: ...
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def area(self) -> int: ...
|
|
55
|
+
|
|
56
|
+
def overlaps_temporally(self, other: Allocation) -> bool: ...
|
|
57
|
+
|
|
58
|
+
def overlaps_spatially(self, other: Allocation) -> bool: ...
|
|
59
|
+
|
|
60
|
+
def overlaps(self, other: Allocation) -> bool: ...
|
|
61
|
+
|
|
62
|
+
def with_offset(self, offset: int) -> Allocation: ...
|
|
63
|
+
|
|
64
|
+
def with_kind(self, kind: BufferKind) -> Allocation: ...
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str: ...
|
|
67
|
+
|
|
68
|
+
def __repr__(self) -> str: ...
|
|
69
|
+
|
|
70
|
+
def __eq__(self, arg: Allocation, /) -> bool: ...
|
|
71
|
+
|
|
72
|
+
def __hash__(self) -> int: ...
|
|
73
|
+
|
|
74
|
+
class GreedyAllocatorCpp:
|
|
75
|
+
def __init__(self) -> None: ...
|
|
76
|
+
|
|
77
|
+
def allocate(self, allocations: Sequence[Allocation]) -> list[Allocation]: ...
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str: ...
|
|
80
|
+
|
|
81
|
+
def __repr__(self) -> str: ...
|
|
82
|
+
|
|
83
|
+
def __eq__(self, arg: GreedyAllocatorCpp, /) -> bool: ...
|
|
84
|
+
|
|
85
|
+
def __hash__(self) -> int: ...
|
omnimalloc/allocate.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import TypeVar, cast
|
|
7
|
+
|
|
8
|
+
from .allocators import BaseAllocator, get_default_allocator
|
|
9
|
+
from .primitives import Memory, Pool, System
|
|
10
|
+
from .validate import validate_allocation
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T", System, Memory, Pool)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_allocation(
|
|
16
|
+
entity: T,
|
|
17
|
+
allocator: BaseAllocator | type[BaseAllocator] | str | None = None,
|
|
18
|
+
validate: bool = False,
|
|
19
|
+
) -> T:
|
|
20
|
+
"""Run allocation on the given entity using the provided allocator.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
entity: The entity to allocate (System, Memory, or Pool).
|
|
24
|
+
allocator: The allocator to use (instance, class, or name).
|
|
25
|
+
validate: Whether to validate the allocated entity.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The allocated entity with the same type as the input.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if allocator is None:
|
|
32
|
+
allocator = get_default_allocator()
|
|
33
|
+
|
|
34
|
+
if isinstance(allocator, str):
|
|
35
|
+
allocator = BaseAllocator.get(allocator)
|
|
36
|
+
|
|
37
|
+
if isinstance(allocator, type):
|
|
38
|
+
allocator = allocator()
|
|
39
|
+
|
|
40
|
+
# At this point, allocator is guaranteed to be a BaseAllocator instance
|
|
41
|
+
assert isinstance(allocator, BaseAllocator)
|
|
42
|
+
|
|
43
|
+
# ty doesn't understand that TypeVar T (System|Memory|Pool) all have allocate method
|
|
44
|
+
allocated = entity.allocate(allocator) # type: ignore[invalid-argument-type]
|
|
45
|
+
|
|
46
|
+
if validate:
|
|
47
|
+
validate_allocation(allocated)
|
|
48
|
+
|
|
49
|
+
return cast("T", allocated)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from .base import BaseAllocator as BaseAllocator
|
|
6
|
+
from .genetic import GeneticAllocator as GeneticAllocator
|
|
7
|
+
from .greedy import GreedyAllocator as GreedyAllocator
|
|
8
|
+
from .greedy import GreedyByAreaAllocator as GreedyByAreaAllocator
|
|
9
|
+
from .greedy import GreedyByConflictAllocator as GreedyByConflictAllocator
|
|
10
|
+
from .greedy import GreedyByDurationAllocator as GreedyByDurationAllocator
|
|
11
|
+
from .greedy import GreedyBySizeAllocator as GreedyBySizeAllocator
|
|
12
|
+
from .greedy_cpp import GreedyAllocatorCpp as GreedyAllocatorCpp
|
|
13
|
+
from .greedy_cpp import GreedyByAreaAllocatorCpp as GreedyByAreaAllocatorCpp
|
|
14
|
+
from .greedy_cpp import GreedyByConflictAllocatorCpp as GreedyByConflictAllocatorCpp
|
|
15
|
+
from .greedy_cpp import GreedyByDurationAllocatorCpp as GreedyByDurationAllocatorCpp
|
|
16
|
+
from .greedy_cpp import GreedyBySizeAllocatorCpp as GreedyBySizeAllocatorCpp
|
|
17
|
+
from .hillclimb import HillClimbAllocator as HillClimbAllocator
|
|
18
|
+
from .minimalloc import MinimallocAllocator as MinimallocAllocator
|
|
19
|
+
from .naive import NaiveAllocator as NaiveAllocator
|
|
20
|
+
from .random import RandomAllocator as RandomAllocator
|
|
21
|
+
from .utils import AVAILABLE_ALLOCATORS as AVAILABLE_ALLOCATORS
|
|
22
|
+
from .utils import DEFAULT_ALLOCATOR as DEFAULT_ALLOCATOR
|
|
23
|
+
from .utils import get_allocator_by_name as get_allocator_by_name
|
|
24
|
+
from .utils import get_available_allocators as get_available_allocators
|
|
25
|
+
from .utils import get_default_allocator as get_default_allocator
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from omnimalloc.common.registry import Registered
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from omnimalloc.primitives import Allocation
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseAllocator(Registered):
|
|
15
|
+
"""Base class for allocators with automatic registry."""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def allocate(
|
|
19
|
+
self, allocations: tuple["Allocation", ...]
|
|
20
|
+
) -> tuple["Allocation", ...]:
|
|
21
|
+
"""Run the allocator on the given allocations."""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def reset(self) -> None:
|
|
25
|
+
"""Optional: reset allocator state/config. Override if needed."""
|
|
26
|
+
...
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from omnimalloc.common.optional import require_optional
|
|
10
|
+
from omnimalloc.primitives import Allocation
|
|
11
|
+
|
|
12
|
+
from .greedy import GreedyAllocator
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from deap import algorithms, base, creator, tools
|
|
16
|
+
|
|
17
|
+
HAS_DEAP = True
|
|
18
|
+
|
|
19
|
+
except ImportError:
|
|
20
|
+
from types import SimpleNamespace
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
HAS_DEAP = False
|
|
24
|
+
|
|
25
|
+
algorithms: Any = SimpleNamespace(
|
|
26
|
+
eaSimple=None,
|
|
27
|
+
)
|
|
28
|
+
base: Any = SimpleNamespace(
|
|
29
|
+
Fitness=None,
|
|
30
|
+
Toolbox=None,
|
|
31
|
+
)
|
|
32
|
+
creator: Any = SimpleNamespace(
|
|
33
|
+
create=None,
|
|
34
|
+
)
|
|
35
|
+
tools: Any = SimpleNamespace(
|
|
36
|
+
initIterate=None,
|
|
37
|
+
selTournament=None,
|
|
38
|
+
cxOrdered=None,
|
|
39
|
+
mutShuffleIndexes=None,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GeneticAllocator(GreedyAllocator):
|
|
44
|
+
"""Genetic algorithm allocator that evolves permutation orders."""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
seed: int = 42,
|
|
49
|
+
population_size: int = 100,
|
|
50
|
+
num_generations: int = 50,
|
|
51
|
+
crossover_prob: float = 0.7,
|
|
52
|
+
mutation_prob: float = 0.2,
|
|
53
|
+
tournament_size: int = 3,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize the genetic allocator."""
|
|
56
|
+
if not HAS_DEAP:
|
|
57
|
+
require_optional("deap", "GeneticAllocator")
|
|
58
|
+
|
|
59
|
+
self.seed = seed
|
|
60
|
+
self.population_size = population_size
|
|
61
|
+
self.num_generations = num_generations
|
|
62
|
+
self.crossover_prob = crossover_prob
|
|
63
|
+
self.mutation_prob = mutation_prob
|
|
64
|
+
self.tournament_size = tournament_size
|
|
65
|
+
|
|
66
|
+
# Setup DEAP creators (only once per class)
|
|
67
|
+
if not hasattr(creator, "FitnessMin"):
|
|
68
|
+
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
|
|
69
|
+
if not hasattr(creator, "Individual"):
|
|
70
|
+
# FitnessMin is dynamically created by DEAP
|
|
71
|
+
creator.create("Individual", list, fitness=creator.FitnessMin) # type: ignore[possibly-missing-attribute]
|
|
72
|
+
|
|
73
|
+
def _evaluate_permutation(
|
|
74
|
+
self, permutation: list[int], allocations: tuple[Allocation, ...]
|
|
75
|
+
) -> tuple[float]:
|
|
76
|
+
"""Evaluate a permutation by computing peak memory usage."""
|
|
77
|
+
|
|
78
|
+
# Apply permutation
|
|
79
|
+
permuted_allocs = tuple(allocations[i] for i in permutation)
|
|
80
|
+
|
|
81
|
+
# Run greedy allocation
|
|
82
|
+
result = super().allocate(permuted_allocs)
|
|
83
|
+
|
|
84
|
+
# Calculate peak memory usage
|
|
85
|
+
if not result:
|
|
86
|
+
return (0.0,)
|
|
87
|
+
|
|
88
|
+
peak_memory = max(alloc.height for alloc in result if alloc.height is not None)
|
|
89
|
+
return (float(peak_memory),)
|
|
90
|
+
|
|
91
|
+
def _create_heuristic_permutations(
|
|
92
|
+
self, allocations: tuple[Allocation, ...]
|
|
93
|
+
) -> list[list[int]]:
|
|
94
|
+
"""Create permutations based on greedy heuristics."""
|
|
95
|
+
|
|
96
|
+
permutations = []
|
|
97
|
+
|
|
98
|
+
# Create index mapping for original order
|
|
99
|
+
indexed_allocs = list(enumerate(allocations))
|
|
100
|
+
|
|
101
|
+
# 1. Greedy by size (largest first)
|
|
102
|
+
sorted_by_size = sorted(indexed_allocs, key=lambda x: x[1].size, reverse=True)
|
|
103
|
+
permutations.append([idx for idx, _ in sorted_by_size])
|
|
104
|
+
|
|
105
|
+
return permutations
|
|
106
|
+
|
|
107
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
108
|
+
"""Evolve permutations using genetic algorithm to find best allocation."""
|
|
109
|
+
|
|
110
|
+
# Set random seeds for deterministic behavior
|
|
111
|
+
random.seed(self.seed)
|
|
112
|
+
np.random.default_rng(self.seed)
|
|
113
|
+
|
|
114
|
+
if not allocations:
|
|
115
|
+
return allocations
|
|
116
|
+
|
|
117
|
+
if len(allocations) == 1:
|
|
118
|
+
return super().allocate(allocations)
|
|
119
|
+
|
|
120
|
+
n = len(allocations)
|
|
121
|
+
|
|
122
|
+
# Setup toolbox
|
|
123
|
+
toolbox = base.Toolbox()
|
|
124
|
+
|
|
125
|
+
# Register individual and population generators
|
|
126
|
+
toolbox.register("indices", random.sample, range(n), n) # Random permutation
|
|
127
|
+
# Individual and indices are dynamically created by DEAP
|
|
128
|
+
toolbox.register(
|
|
129
|
+
"individual",
|
|
130
|
+
tools.initIterate,
|
|
131
|
+
creator.Individual, # type: ignore[possibly-missing-attribute]
|
|
132
|
+
toolbox.indices, # type: ignore[possibly-missing-attribute]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Register genetic operators
|
|
136
|
+
toolbox.register(
|
|
137
|
+
"evaluate", self._evaluate_permutation, allocations=allocations
|
|
138
|
+
)
|
|
139
|
+
toolbox.register("mate", tools.cxOrdered)
|
|
140
|
+
toolbox.register("mutate", tools.mutShuffleIndexes, indpb=0.05)
|
|
141
|
+
# TODO(fpedd): Try larger tournsize and selNSGA2
|
|
142
|
+
toolbox.register("select", tools.selTournament, tournsize=self.tournament_size)
|
|
143
|
+
|
|
144
|
+
# Create initial population with heuristic seeding
|
|
145
|
+
population = []
|
|
146
|
+
|
|
147
|
+
# Add heuristic-based individuals (5 heuristics)
|
|
148
|
+
heuristic_perms = self._create_heuristic_permutations(allocations)
|
|
149
|
+
for perm in heuristic_perms:
|
|
150
|
+
# Individual is dynamically created by DEAP
|
|
151
|
+
individual = creator.Individual(perm) # type: ignore[possibly-missing-attribute]
|
|
152
|
+
population.append(individual)
|
|
153
|
+
|
|
154
|
+
# Fill rest with random individuals
|
|
155
|
+
for _ in range(self.population_size - len(heuristic_perms)):
|
|
156
|
+
# individual() is dynamically registered on toolbox
|
|
157
|
+
individual = toolbox.individual() # type: ignore[possibly-missing-attribute]
|
|
158
|
+
population.append(individual)
|
|
159
|
+
|
|
160
|
+
# Track best individual
|
|
161
|
+
hall_of_fame = tools.HallOfFame(maxsize=1)
|
|
162
|
+
|
|
163
|
+
# Setup statistics
|
|
164
|
+
stats = tools.Statistics(key=lambda ind: ind.fitness.values)
|
|
165
|
+
stats.register("min", np.min)
|
|
166
|
+
stats.register("avg", np.mean)
|
|
167
|
+
|
|
168
|
+
# Run genetic algorithm
|
|
169
|
+
# TODO(fpedd): Try eaMuPlusLambda and eaMuCommaLambda
|
|
170
|
+
population, _ = algorithms.eaSimple(
|
|
171
|
+
population=population,
|
|
172
|
+
toolbox=toolbox,
|
|
173
|
+
cxpb=self.crossover_prob,
|
|
174
|
+
mutpb=self.mutation_prob,
|
|
175
|
+
ngen=self.num_generations,
|
|
176
|
+
stats=stats,
|
|
177
|
+
halloffame=hall_of_fame,
|
|
178
|
+
verbose=False,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Get best permutation
|
|
182
|
+
best_individual = hall_of_fame[0]
|
|
183
|
+
best_permutation = list(best_individual)
|
|
184
|
+
|
|
185
|
+
# Apply best permutation and allocate
|
|
186
|
+
permuted_allocs = tuple(allocations[i] for i in best_permutation)
|
|
187
|
+
return super().allocate(permuted_allocs)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from omnimalloc.primitives import Allocation
|
|
6
|
+
|
|
7
|
+
from .base import BaseAllocator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GreedyAllocator(BaseAllocator):
|
|
11
|
+
"""Base greedy allocator using first-fit strategy."""
|
|
12
|
+
|
|
13
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
14
|
+
placed_allocations: list[Allocation] = []
|
|
15
|
+
|
|
16
|
+
for current_alloc in allocations:
|
|
17
|
+
# Collect overlapping allocations sorted by offset
|
|
18
|
+
overlapping = [
|
|
19
|
+
placed
|
|
20
|
+
for placed in placed_allocations
|
|
21
|
+
if current_alloc.overlaps_temporally(placed)
|
|
22
|
+
]
|
|
23
|
+
overlapping.sort(key=lambda a: a.offset or 0, reverse=False)
|
|
24
|
+
|
|
25
|
+
# Find offset using first-fit (outperforms best-fit in practice)
|
|
26
|
+
best_offset = 0
|
|
27
|
+
for placed in overlapping:
|
|
28
|
+
assert placed.offset is not None
|
|
29
|
+
gap = placed.offset - best_offset
|
|
30
|
+
if gap >= current_alloc.size:
|
|
31
|
+
break
|
|
32
|
+
best_offset = max(best_offset, placed.offset + placed.size)
|
|
33
|
+
|
|
34
|
+
new_alloc = current_alloc.with_offset(best_offset)
|
|
35
|
+
placed_allocations.append(new_alloc)
|
|
36
|
+
|
|
37
|
+
return tuple(placed_allocations)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GreedyByDurationAllocator(GreedyAllocator):
|
|
41
|
+
"""Greedy allocator sorting by duration (longest first)."""
|
|
42
|
+
|
|
43
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
44
|
+
sorted_allocs = sorted(allocations, key=lambda a: a.duration, reverse=True)
|
|
45
|
+
return super().allocate(tuple(sorted_allocs))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GreedyByConflictAllocator(GreedyAllocator):
|
|
49
|
+
"""Greedy allocator sorting by conflict degree (most conflicted first)."""
|
|
50
|
+
|
|
51
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
52
|
+
conflict_degrees = {}
|
|
53
|
+
for alloc in allocations:
|
|
54
|
+
conflicts = sum(
|
|
55
|
+
1
|
|
56
|
+
for other in allocations
|
|
57
|
+
if other != alloc and alloc.overlaps_temporally(other)
|
|
58
|
+
)
|
|
59
|
+
conflict_degrees[alloc] = conflicts
|
|
60
|
+
|
|
61
|
+
sorted_allocs = sorted(
|
|
62
|
+
allocations, key=lambda a: (conflict_degrees[a], a.size), reverse=True
|
|
63
|
+
)
|
|
64
|
+
return super().allocate(tuple(sorted_allocs))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class GreedyByAreaAllocator(GreedyAllocator):
|
|
68
|
+
"""Greedy allocator sorting by area (size * duration, largest first)."""
|
|
69
|
+
|
|
70
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
71
|
+
sorted_allocs = sorted(
|
|
72
|
+
allocations, key=lambda a: a.size * a.duration, reverse=True
|
|
73
|
+
)
|
|
74
|
+
return super().allocate(tuple(sorted_allocs))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class GreedyBySizeAllocator(GreedyAllocator):
|
|
78
|
+
"""Greedy allocator sorting by size (largest first)."""
|
|
79
|
+
|
|
80
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
81
|
+
sorted_allocs = sorted(allocations, key=lambda a: a.size, reverse=True)
|
|
82
|
+
return super().allocate(tuple(sorted_allocs))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from omnimalloc._cpp import GreedyAllocatorCpp as _GreedyAllocatorCpp
|
|
6
|
+
from omnimalloc.primitives import Allocation
|
|
7
|
+
|
|
8
|
+
from .base import BaseAllocator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GreedyAllocatorCpp(BaseAllocator):
|
|
12
|
+
"""C++ implementation of the base greedy allocator using first-fit strategy."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self._cpp_allocator = _GreedyAllocatorCpp()
|
|
16
|
+
|
|
17
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
18
|
+
allocs_list = list(allocations)
|
|
19
|
+
result = self._cpp_allocator.allocate(allocs_list)
|
|
20
|
+
return tuple(result)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GreedyByDurationAllocatorCpp(GreedyAllocatorCpp):
|
|
24
|
+
"""C++ greedy allocator sorting by duration (longest first)."""
|
|
25
|
+
|
|
26
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
27
|
+
sorted_allocs = sorted(allocations, key=lambda a: a.duration, reverse=True)
|
|
28
|
+
return super().allocate(tuple(sorted_allocs))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GreedyByConflictAllocatorCpp(GreedyAllocatorCpp):
|
|
32
|
+
"""C++ greedy allocator sorting by conflict degree (most conflicted first)."""
|
|
33
|
+
|
|
34
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
35
|
+
conflict_degrees = {}
|
|
36
|
+
for alloc in allocations:
|
|
37
|
+
conflicts = sum(
|
|
38
|
+
1
|
|
39
|
+
for other in allocations
|
|
40
|
+
if other != alloc and alloc.overlaps_temporally(other)
|
|
41
|
+
)
|
|
42
|
+
conflict_degrees[alloc] = conflicts
|
|
43
|
+
|
|
44
|
+
sorted_allocs = sorted(
|
|
45
|
+
allocations, key=lambda a: (conflict_degrees[a], a.size), reverse=True
|
|
46
|
+
)
|
|
47
|
+
return super().allocate(tuple(sorted_allocs))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GreedyByAreaAllocatorCpp(GreedyAllocatorCpp):
|
|
51
|
+
"""C++ greedy allocator sorting by area (size * duration, largest first)."""
|
|
52
|
+
|
|
53
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
54
|
+
sorted_allocs = sorted(
|
|
55
|
+
allocations, key=lambda a: a.size * a.duration, reverse=True
|
|
56
|
+
)
|
|
57
|
+
return super().allocate(tuple(sorted_allocs))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GreedyBySizeAllocatorCpp(GreedyAllocatorCpp):
|
|
61
|
+
"""C++ greedy allocator sorting by size (largest first)."""
|
|
62
|
+
|
|
63
|
+
def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
|
|
64
|
+
sorted_allocs = sorted(allocations, key=lambda a: a.size, reverse=True)
|
|
65
|
+
return super().allocate(tuple(sorted_allocs))
|