omnimalloc 0.2.0__tar.gz → 0.4.0__tar.gz

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.
Files changed (114) hide show
  1. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/CMakeLists.txt +9 -3
  2. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/PKG-INFO +1 -1
  3. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/pyproject.toml +10 -1
  4. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/primitives/id_type.hpp +1 -0
  5. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/__init__.py +2 -0
  6. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/greedy.py +39 -0
  7. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/greedy_cpp.py +15 -0
  8. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/__init__.py +1 -0
  9. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/sources/__init__.py +1 -0
  10. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/sources/minimalloc.py +21 -27
  11. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/common/registry.py +3 -2
  12. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/primitives/allocation.py +1 -0
  13. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/allocators/test_greedy.py +56 -0
  14. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/allocators/test_greedy_cpp.py +59 -1
  15. omnimalloc-0.4.0/tests/unit/benchmark/sources/test_minimalloc.py +107 -0
  16. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/uv.lock +1 -1
  17. omnimalloc-0.2.0/tests/unit/benchmark/sources/test_minimalloc.py +0 -121
  18. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/.clang-format +0 -0
  19. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/.clang-tidy +0 -0
  20. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/.gitignore +0 -0
  21. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/.pre-commit-config.yaml +0 -0
  22. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/.python-version +0 -0
  23. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/LICENSE +0 -0
  24. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/README.md +0 -0
  25. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/examples/01_basic.py +0 -0
  26. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/examples/02_plotting.py +0 -0
  27. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/examples/03_allocators.py +0 -0
  28. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/examples/04_sources.py +0 -0
  29. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/examples/05_benchmark.py +0 -0
  30. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/examples/README.md +0 -0
  31. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/allocators/greedy.cpp +0 -0
  32. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/allocators/greedy.hpp +0 -0
  33. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/bindings.cpp +0 -0
  34. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/primitives/allocation.cpp +0 -0
  35. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/primitives/allocation.hpp +0 -0
  36. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/primitives/buffer_kind.hpp +0 -0
  37. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/cpp/primitives/hash_utils.hpp +0 -0
  38. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/__init__.py +0 -0
  39. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/_cpp.pyi +0 -0
  40. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocate.py +0 -0
  41. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/base.py +0 -0
  42. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/genetic.py +0 -0
  43. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/hillclimb.py +0 -0
  44. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/minimalloc.py +0 -0
  45. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/naive.py +0 -0
  46. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/random.py +0 -0
  47. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/allocators/utils.py +0 -0
  48. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/benchmark.py +0 -0
  49. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/converters/__init__.py +0 -0
  50. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/converters/model.py +0 -0
  51. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/converters/onnx.py +0 -0
  52. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/__init__.py +0 -0
  53. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/campaign.py +0 -0
  54. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/export.py +0 -0
  55. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/report.py +0 -0
  56. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/result.py +0 -0
  57. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/utils.py +0 -0
  58. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/results/visualize.py +0 -0
  59. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/sources/base.py +0 -0
  60. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/sources/generator.py +0 -0
  61. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/sources/huggingface.py +0 -0
  62. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/sources/utils.py +0 -0
  63. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/timer.py +0 -0
  64. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/benchmark/utils.py +0 -0
  65. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/common/__init__.py +0 -0
  66. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/common/directories.py +0 -0
  67. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/common/optional.py +0 -0
  68. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/common/units.py +0 -0
  69. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/primitives/__init__.py +0 -0
  70. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/primitives/memory.py +0 -0
  71. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/primitives/pool.py +0 -0
  72. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/primitives/system.py +0 -0
  73. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/primitives/utils.py +0 -0
  74. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/py.typed +0 -0
  75. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/validate.py +0 -0
  76. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/src/python/omnimalloc/visualize.py +0 -0
  77. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/__init__.py +0 -0
  78. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/conftest.py +0 -0
  79. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/integration/__init__.py +0 -0
  80. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/integration/test_allocators_on_sources.py +0 -0
  81. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/integration/test_examples.py +0 -0
  82. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/integration/test_notebooks.py +0 -0
  83. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/__init__.py +0 -0
  84. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/allocators/__init__.py +0 -0
  85. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/allocators/test_naive.py +0 -0
  86. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/__init__.py +0 -0
  87. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/converters/__init__.py +0 -0
  88. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/converters/test_model.py +0 -0
  89. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/converters/test_onnx.py +0 -0
  90. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/results/__init__.py +0 -0
  91. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/results/test_campaign.py +0 -0
  92. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/results/test_export.py +0 -0
  93. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/results/test_report.py +0 -0
  94. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/results/test_result.py +0 -0
  95. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/results/test_visualize.py +0 -0
  96. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/sources/__init__.py +0 -0
  97. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/sources/test_base.py +0 -0
  98. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/sources/test_generator.py +0 -0
  99. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/sources/test_huggingface.py +0 -0
  100. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/test_benchmark.py +0 -0
  101. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/benchmark/test_timer.py +0 -0
  102. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/common/__init__.py +0 -0
  103. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/common/test_directories.py +0 -0
  104. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/common/test_registry.py +0 -0
  105. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/__init__.py +0 -0
  106. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/test_allocation.py +0 -0
  107. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/test_bufferkind.py +0 -0
  108. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/test_memory.py +0 -0
  109. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/test_pool.py +0 -0
  110. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/test_system.py +0 -0
  111. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/primitives/test_utils.py +0 -0
  112. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/test_allocate.py +0 -0
  113. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/test_validate.py +0 -0
  114. {omnimalloc-0.2.0 → omnimalloc-0.4.0}/tests/unit/test_visualize.py +0 -0
@@ -1,7 +1,7 @@
1
1
  cmake_minimum_required(VERSION 3.18...3.30)
2
2
  project(
3
3
  OmniMalloc
4
- VERSION 0.2.0 # Also update pyproject.toml
4
+ VERSION 0.4.0 # Also update pyproject.toml
5
5
  LANGUAGES CXX)
6
6
 
7
7
  set(CMAKE_CXX_STANDARD 20)
@@ -11,13 +11,11 @@ set(CMAKE_CXX_EXTENSIONS OFF)
11
11
  option(ENABLE_CLANG_TIDY "Run clang-tidy with the compiler" OFF)
12
12
  if(ENABLE_CLANG_TIDY)
13
13
  find_program(CLANG_TIDY_PROGRAM NAMES clang-tidy REQUIRED)
14
- set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_PROGRAM}")
15
14
  endif()
16
15
 
17
16
  option(ENABLE_IWYU "Run include-what-you-use with the compiler" OFF)
18
17
  if(ENABLE_IWYU)
19
18
  find_program(IWYU_PROGRAM NAMES include-what-you-use iwyu REQUIRED)
20
- set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${IWYU_PROGRAM}")
21
19
  endif()
22
20
 
23
21
  find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module)
@@ -33,6 +31,14 @@ nanobind_add_module(_cpp NB_STATIC src/cpp/allocators/greedy.cpp
33
31
 
34
32
  target_include_directories(_cpp PRIVATE src/cpp)
35
33
 
34
+ if(ENABLE_CLANG_TIDY)
35
+ set_target_properties(_cpp PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_PROGRAM}")
36
+ endif()
37
+ if(ENABLE_IWYU)
38
+ set_target_properties(_cpp PROPERTIES CXX_INCLUDE_WHAT_YOU_USE
39
+ "${IWYU_PROGRAM}")
40
+ endif()
41
+
36
42
  nanobind_add_stub(
37
43
  _cpp_stub
38
44
  MODULE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omnimalloc
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Your one-stop shop for static memory allocation.
5
5
  Keywords: memory,allocation,allocator,static-allocation
6
6
  Author-Email: Fabian Peddinghaus <fabianpedd@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "omnimalloc"
7
- version = "0.2.0" # Also update CMakeLists.txt
7
+ version = "0.4.0" # Also update CMakeLists.txt
8
8
  description = "Your one-stop shop for static memory allocation."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.10"
@@ -73,8 +73,12 @@ skip = "*-musllinux_*"
73
73
  # test-command = "pytest {project}/tests/unit -x --no-header -q"
74
74
 
75
75
  [tool.cibuildwheel.linux]
76
+ archs = ["auto"]
76
77
  before-all = "yum install -y cmake || apk add cmake"
77
78
 
79
+ [tool.cibuildwheel.macos]
80
+ archs = ["arm64"]
81
+
78
82
  [tool.ruff]
79
83
  line-length = 88
80
84
  target-version = "py310"
@@ -113,6 +117,7 @@ ignore = [
113
117
  "PLC0415", # import outside top-level
114
118
  "PLR0913", # too many arguments
115
119
  "PLR2004", # magic value comparison
120
+ "PLW2901", # reassign for loop vars
116
121
  "PYI019", # custom TypeVar
117
122
  "RET504", # unnecessary variable before return
118
123
  "S101", # assert
@@ -128,6 +133,10 @@ ignore = [
128
133
  [tool.pytest.ini_options]
129
134
  testpaths = ["tests"]
130
135
  addopts = ["--import-mode=importlib"]
136
+ log_cli = true
137
+ log_cli_level = "INFO"
138
+ log_cli_format = "%(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)d)"
139
+ log_cli_date_format = "%Y-%m-%d %H:%M:%S"
131
140
 
132
141
  [tool.ty]
133
142
 
@@ -12,6 +12,7 @@
12
12
 
13
13
  namespace omnimalloc {
14
14
 
15
+ // Must match IdType in src/python/omnimalloc/primitives/allocation.py
15
16
  using IdType = std::variant<int64_t, std::string>;
16
17
 
17
18
  struct IdTypeHash {
@@ -5,11 +5,13 @@
5
5
  from .base import BaseAllocator as BaseAllocator
6
6
  from .genetic import GeneticAllocator as GeneticAllocator
7
7
  from .greedy import GreedyAllocator as GreedyAllocator
8
+ from .greedy import GreedyByAllAllocator as GreedyByAllAllocator
8
9
  from .greedy import GreedyByAreaAllocator as GreedyByAreaAllocator
9
10
  from .greedy import GreedyByConflictAllocator as GreedyByConflictAllocator
10
11
  from .greedy import GreedyByDurationAllocator as GreedyByDurationAllocator
11
12
  from .greedy import GreedyBySizeAllocator as GreedyBySizeAllocator
12
13
  from .greedy_cpp import GreedyAllocatorCpp as GreedyAllocatorCpp
14
+ from .greedy_cpp import GreedyByAllAllocatorCpp as GreedyByAllAllocatorCpp
13
15
  from .greedy_cpp import GreedyByAreaAllocatorCpp as GreedyByAreaAllocatorCpp
14
16
  from .greedy_cpp import GreedyByConflictAllocatorCpp as GreedyByConflictAllocatorCpp
15
17
  from .greedy_cpp import GreedyByDurationAllocatorCpp as GreedyByDurationAllocatorCpp
@@ -2,6 +2,8 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
 
5
+ import sys
6
+
5
7
  from omnimalloc.primitives import Allocation
6
8
 
7
9
  from .base import BaseAllocator
@@ -80,3 +82,40 @@ class GreedyBySizeAllocator(GreedyAllocator):
80
82
  def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
81
83
  sorted_allocs = sorted(allocations, key=lambda a: a.size, reverse=True)
82
84
  return super().allocate(tuple(sorted_allocs))
85
+
86
+
87
+ def allocate_best_of(
88
+ variants: tuple[BaseAllocator, ...], allocations: tuple[Allocation, ...]
89
+ ) -> tuple[Allocation, ...]:
90
+ """Run each variant and return the result with the smallest peak memory."""
91
+ if not allocations:
92
+ return allocations
93
+
94
+ best_allocation: tuple[Allocation, ...] | None = None
95
+ best_peak_memory = sys.maxsize
96
+
97
+ for variant in variants:
98
+ result = variant.allocate(allocations)
99
+ heights = [a.height for a in result if a.height is not None]
100
+ peak_memory = max(heights) if heights else 0
101
+
102
+ if peak_memory < best_peak_memory:
103
+ best_peak_memory = peak_memory
104
+ best_allocation = result
105
+
106
+ assert best_allocation is not None
107
+ return best_allocation
108
+
109
+
110
+ class GreedyByAllAllocator(GreedyAllocator):
111
+ """Greedy allocator that runs every variant and keeps the best result."""
112
+
113
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
114
+ variants: tuple[BaseAllocator, ...] = (
115
+ GreedyAllocator(),
116
+ GreedyBySizeAllocator(),
117
+ GreedyByDurationAllocator(),
118
+ GreedyByAreaAllocator(),
119
+ GreedyByConflictAllocator(),
120
+ )
121
+ return allocate_best_of(variants, allocations)
@@ -6,6 +6,7 @@ from omnimalloc._cpp import GreedyAllocatorCpp as _GreedyAllocatorCpp
6
6
  from omnimalloc.primitives import Allocation
7
7
 
8
8
  from .base import BaseAllocator
9
+ from .greedy import allocate_best_of
9
10
 
10
11
 
11
12
  class GreedyAllocatorCpp(BaseAllocator):
@@ -63,3 +64,17 @@ class GreedyBySizeAllocatorCpp(GreedyAllocatorCpp):
63
64
  def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
64
65
  sorted_allocs = sorted(allocations, key=lambda a: a.size, reverse=True)
65
66
  return super().allocate(tuple(sorted_allocs))
67
+
68
+
69
+ class GreedyByAllAllocatorCpp(GreedyAllocatorCpp):
70
+ """C++ greedy allocator that runs every variant and keeps the best result."""
71
+
72
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
73
+ variants: tuple[BaseAllocator, ...] = (
74
+ GreedyAllocatorCpp(),
75
+ GreedyBySizeAllocatorCpp(),
76
+ GreedyByDurationAllocatorCpp(),
77
+ GreedyByAreaAllocatorCpp(),
78
+ GreedyByConflictAllocatorCpp(),
79
+ )
80
+ return allocate_best_of(variants, allocations)
@@ -13,6 +13,7 @@ from .sources import BaseSource as BaseSource
13
13
  from .sources import HighContentionSource as HighContentionSource
14
14
  from .sources import HuggingfaceSource as HuggingfaceSource
15
15
  from .sources import MinimallocSource as MinimallocSource
16
+ from .sources import MinimallocSubset as MinimallocSubset
16
17
  from .sources import PowerOf2Source as PowerOf2Source
17
18
  from .sources import RandomSource as RandomSource
18
19
  from .sources import SequentialSource as SequentialSource
@@ -10,6 +10,7 @@ from .generator import SequentialSource as SequentialSource
10
10
  from .generator import UniformSource as UniformSource
11
11
  from .huggingface import HuggingfaceSource as HuggingfaceSource
12
12
  from .minimalloc import MinimallocSource as MinimallocSource
13
+ from .minimalloc import MinimallocSubset as MinimallocSubset
13
14
  from .utils import AVAILABLE_SOURCES as AVAILABLE_SOURCES
14
15
  from .utils import DEFAULT_SOURCE as DEFAULT_SOURCE
15
16
  from .utils import get_available_sources as get_available_sources
@@ -5,6 +5,7 @@
5
5
  import csv
6
6
  import logging
7
7
  from dataclasses import dataclass
8
+ from enum import Enum
8
9
  from pathlib import Path
9
10
 
10
11
  from omnimalloc.common.directories import EXTERNAL_DIR
@@ -15,6 +16,14 @@ from .base import BaseSource
15
16
  logger = logging.getLogger(__name__)
16
17
 
17
18
 
19
+ class MinimallocSubset(str, Enum):
20
+ """Bundled CSV subsets shipped under ``external/minimalloc/<value>``."""
21
+
22
+ EXAMPLES = "examples"
23
+ SMALL = "small"
24
+ CHALLENGING = "challenging"
25
+
26
+
18
27
  @dataclass(frozen=True)
19
28
  class _MinimallocBuffer:
20
29
  id: IdType
@@ -70,29 +79,20 @@ def _from_minimalloc_csv(file_path: str | Path) -> Pool:
70
79
  return pool
71
80
 
72
81
 
73
- def _get_minimalloc_pools() -> list[Pool]:
74
- csv_dir = EXTERNAL_DIR / "minimalloc" / "challenging"
75
- csv_files = list(csv_dir.glob("*.csv"))
76
- pools = [_from_minimalloc_csv(file) for file in csv_files]
77
- return pools
78
-
79
-
80
82
  class MinimallocSource(BaseSource):
81
- """Load allocations from Minimalloc CSV format.
83
+ """Load allocations from a bundled Minimalloc CSV subset.
82
84
 
83
- This is a fixed source with predetermined pools from Minimalloc benchmarks.
84
- Can be initialized with either a specific CSV file or a directory of CSVs.
85
+ This is a fixed source with predetermined pools from the Minimalloc
86
+ benchmarks. Pick a bundled ``subset`` to select which pools to load.
85
87
  """
86
88
 
87
- def __init__(self, file_path: str | Path | None = None) -> None:
88
- self.file_path = Path(file_path) if file_path is not None else None
89
+ def __init__(
90
+ self,
91
+ subset: MinimallocSubset | str = MinimallocSubset.CHALLENGING,
92
+ ) -> None:
93
+ self.subset = MinimallocSubset(subset)
89
94
  self._cached_pools: list[Pool] | None = None
90
95
 
91
- # Validate path exists if provided
92
- if self.file_path is not None and not self.file_path.exists():
93
- msg = f"Path does not exist: {self.file_path}"
94
- raise FileNotFoundError(msg)
95
-
96
96
  # Load pools to get actual num_allocations
97
97
  pools = self._pools
98
98
  num_allocs = sum(len(p.allocations) for p in pools) if pools else 1
@@ -103,16 +103,10 @@ class MinimallocSource(BaseSource):
103
103
  @property
104
104
  def _pools(self) -> list[Pool]:
105
105
  if self._cached_pools is None:
106
- if self.file_path is None:
107
- self._cached_pools = _get_minimalloc_pools()
108
- elif self.file_path.is_file():
109
- self._cached_pools = [_from_minimalloc_csv(self.file_path)]
110
- elif self.file_path.is_dir():
111
- csv_files = list(self.file_path.glob("*.csv"))
112
- self._cached_pools = [_from_minimalloc_csv(f) for f in csv_files]
113
- else:
114
- msg = f"Path does not exist: {self.file_path}"
115
- raise FileNotFoundError(msg)
106
+ csv_dir = EXTERNAL_DIR / "minimalloc" / self.subset.value
107
+ self._cached_pools = [
108
+ _from_minimalloc_csv(f) for f in csv_dir.glob("*.csv")
109
+ ]
116
110
  return self._cached_pools
117
111
 
118
112
  def _all_allocations(self) -> tuple[Allocation, ...]:
@@ -12,8 +12,9 @@ from typing_extensions import Self
12
12
  class Registered(ABC):
13
13
  """Mixin for auto-registering and managing subclasses.
14
14
 
15
- Any direct subclass of will maintain its own registry. Any subclass of that
16
- subclass that's not abstract will be registered in the subclass's registry.
15
+ Any direct subclass of Registered will maintain its own registry. Any
16
+ subclass of that subclass that's not abstract will be registered in the
17
+ direct subclass's registry.
17
18
  """
18
19
 
19
20
  _registry: ClassVar[dict[str, type[Self]]]
@@ -5,6 +5,7 @@
5
5
  from omnimalloc._cpp import Allocation, BufferKind
6
6
 
7
7
  # Type alias for allocation identifiers (int or str)
8
+ # Must match IdType in src/cpp/primitives/id_type.hpp
8
9
  IdType = int | str
9
10
 
10
11
  __all__ = ["Allocation", "BufferKind", "IdType"]
@@ -4,6 +4,7 @@
4
4
 
5
5
  from omnimalloc.allocators.greedy import (
6
6
  GreedyAllocator,
7
+ GreedyByAllAllocator,
7
8
  GreedyByAreaAllocator,
8
9
  GreedyByConflictAllocator,
9
10
  GreedyByDurationAllocator,
@@ -254,3 +255,58 @@ def test_greedy_allocator_fits_in_gap() -> None:
254
255
  assert result[0].offset == 0
255
256
  assert result[1].offset == 50
256
257
  assert result[2].offset == 100
258
+
259
+
260
+ def test_greedy_by_all_empty() -> None:
261
+ allocator = GreedyByAllAllocator()
262
+ result = allocator.allocate(())
263
+ assert len(result) == 0
264
+
265
+
266
+ def test_greedy_by_all_preserves_allocations() -> None:
267
+ allocator = GreedyByAllAllocator()
268
+ allocs = (
269
+ Allocation(id=1, size=100, start=0, end=10),
270
+ Allocation(id=2, size=50, start=5, end=15),
271
+ )
272
+ result = allocator.allocate(allocs)
273
+ assert len(result) == len(allocs)
274
+ assert {a.id for a in result} == {1, 2}
275
+ assert all(a.offset is not None for a in result)
276
+
277
+
278
+ def test_greedy_by_all_picks_best_peak() -> None:
279
+ allocator = GreedyByAllAllocator()
280
+ allocs = (
281
+ Allocation(id=1, size=100, start=0, end=5),
282
+ Allocation(id=2, size=100, start=3, end=8),
283
+ Allocation(id=3, size=100, start=6, end=10),
284
+ Allocation(id=4, size=50, start=0, end=10),
285
+ Allocation(id=5, size=300, start=2, end=4),
286
+ )
287
+ result = allocator.allocate(allocs)
288
+ peak = max(a.height for a in result if a.height is not None)
289
+
290
+ variants = (
291
+ GreedyAllocator(),
292
+ GreedyBySizeAllocator(),
293
+ GreedyByDurationAllocator(),
294
+ GreedyByAreaAllocator(),
295
+ GreedyByConflictAllocator(),
296
+ )
297
+ best_variant_peak = min(
298
+ max(a.height for a in v.allocate(allocs) if a.height is not None)
299
+ for v in variants
300
+ )
301
+ assert peak == best_variant_peak
302
+
303
+
304
+ def test_greedy_by_all_deterministic() -> None:
305
+ allocator = GreedyByAllAllocator()
306
+ allocs = tuple(
307
+ Allocation(id=i, size=(i % 5 + 1) * 100, start=0, end=i % 7 + 1)
308
+ for i in range(20)
309
+ )
310
+ result1 = allocator.allocate(allocs)
311
+ result2 = allocator.allocate(allocs)
312
+ assert all(r1.offset == r2.offset for r1, r2 in zip(result1, result2, strict=True))
@@ -2,9 +2,10 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
 
5
- from omnimalloc.allocators.greedy import GreedyAllocator
5
+ from omnimalloc.allocators.greedy import GreedyAllocator, GreedyByAllAllocator
6
6
  from omnimalloc.allocators.greedy_cpp import (
7
7
  GreedyAllocatorCpp,
8
+ GreedyByAllAllocatorCpp,
8
9
  GreedyByAreaAllocatorCpp,
9
10
  GreedyByConflictAllocatorCpp,
10
11
  GreedyByDurationAllocatorCpp,
@@ -285,3 +286,60 @@ def test_greedy_cpp_matches_python() -> None:
285
286
  for cpp_alloc, py_alloc in zip(cpp_result, py_result, strict=True):
286
287
  assert cpp_alloc.offset == py_alloc.offset
287
288
  assert cpp_alloc.id == py_alloc.id
289
+
290
+
291
+ def test_greedy_cpp_by_all_empty() -> None:
292
+ allocator = GreedyByAllAllocatorCpp()
293
+ result = allocator.allocate(())
294
+ assert len(result) == 0
295
+
296
+
297
+ def test_greedy_cpp_by_all_preserves_allocations() -> None:
298
+ allocator = GreedyByAllAllocatorCpp()
299
+ allocs = (
300
+ Allocation(id=1, size=100, start=0, end=10),
301
+ Allocation(id=2, size=50, start=5, end=15),
302
+ )
303
+ result = allocator.allocate(allocs)
304
+ assert len(result) == len(allocs)
305
+ assert {a.id for a in result} == {1, 2}
306
+ assert all(a.offset is not None for a in result)
307
+
308
+
309
+ def test_greedy_cpp_by_all_deterministic() -> None:
310
+ allocator = GreedyByAllAllocatorCpp()
311
+ allocs = tuple(
312
+ Allocation(id=i, size=(i % 5 + 1) * 100, start=0, end=i % 7 + 1)
313
+ for i in range(20)
314
+ )
315
+ result1 = allocator.allocate(allocs)
316
+ result2 = allocator.allocate(allocs)
317
+ assert all(r1.offset == r2.offset for r1, r2 in zip(result1, result2, strict=True))
318
+
319
+
320
+ def test_greedy_cpp_by_all_matches_python() -> None:
321
+ """C++ greedy-by-all should match the Python greedy-by-all result."""
322
+ cpp_allocator = GreedyByAllAllocatorCpp()
323
+ py_allocator = GreedyByAllAllocator()
324
+
325
+ test_cases = [
326
+ tuple(Allocation(id=i, size=100, start=0, end=10) for i in range(5)),
327
+ (
328
+ Allocation(id=1, size=100, start=0, end=5),
329
+ Allocation(id=2, size=100, start=3, end=8),
330
+ Allocation(id=3, size=100, start=6, end=10),
331
+ Allocation(id=4, size=50, start=0, end=10),
332
+ Allocation(id=5, size=300, start=2, end=4),
333
+ ),
334
+ tuple(
335
+ Allocation(id=i, size=(i % 5 + 1) * 100, start=0, end=i % 7 + 1)
336
+ for i in range(20)
337
+ ),
338
+ ]
339
+
340
+ for allocs in test_cases:
341
+ cpp_result = cpp_allocator.allocate(allocs)
342
+ py_result = py_allocator.allocate(allocs)
343
+ cpp_peak = max(a.height for a in cpp_result if a.height is not None)
344
+ py_peak = max(a.height for a in py_result if a.height is not None)
345
+ assert cpp_peak == py_peak
@@ -0,0 +1,107 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ import pytest
6
+ from omnimalloc.benchmark.sources.minimalloc import MinimallocSource, MinimallocSubset
7
+ from omnimalloc.primitives import BufferKind
8
+
9
+
10
+ def test_minimalloc_source_default_subset_is_challenging() -> None:
11
+ source = MinimallocSource()
12
+ assert source.subset is MinimallocSubset.CHALLENGING
13
+ assert source.num_allocations > 0
14
+
15
+
16
+ def test_minimalloc_source_accepts_enum_member() -> None:
17
+ source = MinimallocSource(MinimallocSubset.SMALL)
18
+ assert source.subset is MinimallocSubset.SMALL
19
+
20
+
21
+ def test_minimalloc_source_accepts_string_alias() -> None:
22
+ """Raw strings are coerced to the matching enum member."""
23
+ source = MinimallocSource("small")
24
+ assert source.subset is MinimallocSubset.SMALL
25
+ assert source.subset == "small"
26
+
27
+
28
+ def test_minimalloc_source_examples_subset() -> None:
29
+ source = MinimallocSource(subset="examples")
30
+ assert source.subset == "examples"
31
+ variants = source.get_available_variants()
32
+ assert len(variants) == 1 # Only one example pool
33
+
34
+
35
+ def test_minimalloc_source_small_subset() -> None:
36
+ source = MinimallocSource(subset="small")
37
+ assert source.subset == "small"
38
+ variants = source.get_available_variants()
39
+ assert len(variants) > 0
40
+ assert all(v[0].islower() for v in variants)
41
+
42
+
43
+ def test_minimalloc_source_challenging_subset() -> None:
44
+ source = MinimallocSource(subset="challenging")
45
+ variants = source.get_available_variants()
46
+ assert len(variants) > 0
47
+
48
+
49
+ def test_minimalloc_source_subsets_are_disjoint() -> None:
50
+ examples = set(MinimallocSource(subset="examples").get_available_variants())
51
+ small = set(MinimallocSource(subset="small").get_available_variants())
52
+ challenging = set(MinimallocSource(subset="challenging").get_available_variants())
53
+ assert examples
54
+ assert small
55
+ assert challenging
56
+ assert examples.isdisjoint(small)
57
+ assert examples.isdisjoint(challenging)
58
+ assert small.isdisjoint(challenging)
59
+
60
+
61
+ def test_minimalloc_source_invalid_subset() -> None:
62
+ with pytest.raises(ValueError, match="not a valid MinimallocSubset"):
63
+ MinimallocSource(subset="bogus") # type: ignore[arg-type]
64
+
65
+
66
+ def test_minimalloc_source_get_allocations_skip_past_end() -> None:
67
+ source = MinimallocSource(subset="examples")
68
+ allocations = source.get_allocations(skip=10**9)
69
+ assert len(allocations) == 0
70
+
71
+
72
+ def test_minimalloc_source_get_pools_with_skip_past_end() -> None:
73
+ source = MinimallocSource(subset="examples")
74
+ pools = source.get_pools(skip=10)
75
+ assert len(pools) == 0
76
+
77
+
78
+ def test_minimalloc_source_get_pools_count_zero() -> None:
79
+ source = MinimallocSource(subset="examples")
80
+ pools = source.get_pools(num_pools=0)
81
+ assert len(pools) == 0
82
+
83
+
84
+ def test_minimalloc_source_get_allocation_workspace_kind() -> None:
85
+ """All loaded allocations are tagged as WORKSPACE buffers."""
86
+ source = MinimallocSource(subset="examples")
87
+ allocation = source.get_allocation()
88
+ assert allocation.kind == BufferKind.WORKSPACE
89
+
90
+
91
+ def test_minimalloc_source_get_variant_by_id() -> None:
92
+ source = MinimallocSource(subset="small")
93
+ variants = source.get_available_variants()
94
+ pool = source.get_variant(variants[0])
95
+ assert pool.id == variants[0]
96
+
97
+
98
+ def test_minimalloc_source_get_variant_by_index() -> None:
99
+ source = MinimallocSource(subset="small")
100
+ pool = source.get_variant(0)
101
+ assert pool.id in source.get_available_variants()
102
+
103
+
104
+ def test_minimalloc_source_get_variant_unknown_id() -> None:
105
+ source = MinimallocSource(subset="examples")
106
+ with pytest.raises(ValueError, match="not found"):
107
+ source.get_variant("does-not-exist")
@@ -1551,7 +1551,7 @@ wheels = [
1551
1551
 
1552
1552
  [[package]]
1553
1553
  name = "omnimalloc"
1554
- version = "0.2.0"
1554
+ version = "0.4.0"
1555
1555
  source = { editable = "." }
1556
1556
  dependencies = [
1557
1557
  { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
@@ -1,121 +0,0 @@
1
- #
2
- # SPDX-License-Identifier: Apache-2.0
3
- #
4
-
5
- import tempfile
6
- from pathlib import Path
7
-
8
- import pytest
9
- from omnimalloc.benchmark.sources.minimalloc import MinimallocSource
10
- from omnimalloc.primitives import BufferKind
11
-
12
-
13
- @pytest.fixture
14
- def sample_csv_path() -> Path:
15
- """Create a temporary CSV file with sample minimalloc data."""
16
- content = """id,lower,upper,size
17
- 0,0,3,4
18
- 1,3,9,4
19
- 2,0,9,8
20
- 3,9,21,4
21
- 4,0,21,16
22
- """
23
- with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
24
- f.write(content)
25
- return Path(f.name)
26
-
27
-
28
- def test_minimalloc_source_basic_creation(sample_csv_path: Path) -> None:
29
- source = MinimallocSource(sample_csv_path)
30
- assert source.file_path == sample_csv_path
31
- assert source.num_allocations == 5
32
-
33
-
34
- def test_minimalloc_source_get_allocations(sample_csv_path: Path) -> None:
35
- source = MinimallocSource(sample_csv_path)
36
- allocations = source.get_allocations()
37
- assert len(allocations) == 5
38
- assert allocations[0].id == "0"
39
- assert allocations[0].start == 0
40
- assert allocations[0].end == 3
41
- assert allocations[0].size == 4
42
- assert allocations[0].kind == BufferKind.WORKSPACE
43
-
44
-
45
- def test_minimalloc_source_get_allocations_with_count(sample_csv_path: Path) -> None:
46
- source = MinimallocSource(sample_csv_path)
47
- allocations = source.get_allocations(num_allocations=3)
48
- assert len(allocations) == 3
49
- assert allocations[0].id == "0"
50
- assert allocations[2].id == "2"
51
-
52
-
53
- def test_minimalloc_source_get_allocations_with_skip(sample_csv_path: Path) -> None:
54
- source = MinimallocSource(sample_csv_path)
55
- allocations = source.get_allocations(skip=2)
56
- assert len(allocations) == 3
57
- assert allocations[0].id == "2"
58
- assert allocations[2].id == "4"
59
-
60
-
61
- def test_minimalloc_source_get_allocations_skip_and_count(
62
- sample_csv_path: Path,
63
- ) -> None:
64
- source = MinimallocSource(sample_csv_path)
65
- allocations = source.get_allocations(num_allocations=2, skip=1)
66
- assert len(allocations) == 2
67
- assert allocations[0].id == "1"
68
- assert allocations[1].id == "2"
69
-
70
-
71
- def test_minimalloc_source_get_allocations_skip_past_end(sample_csv_path: Path) -> None:
72
- source = MinimallocSource(sample_csv_path)
73
- allocations = source.get_allocations(skip=10)
74
- assert len(allocations) == 0
75
-
76
-
77
- def test_minimalloc_source_get_pools(sample_csv_path: Path) -> None:
78
- source = MinimallocSource(sample_csv_path)
79
- pools = source.get_pools()
80
- assert len(pools) == 1
81
- assert len(pools[0].allocations) == 5
82
- assert pools[0].id == sample_csv_path.stem
83
-
84
-
85
- def test_minimalloc_source_get_pools_with_skip(sample_csv_path: Path) -> None:
86
- source = MinimallocSource(sample_csv_path)
87
- pools = source.get_pools(skip=1)
88
- assert len(pools) == 0
89
-
90
-
91
- def test_minimalloc_source_get_pools_count_zero(sample_csv_path: Path) -> None:
92
- source = MinimallocSource(sample_csv_path)
93
- pools = source.get_pools(num_pools=0)
94
- assert len(pools) == 0
95
-
96
-
97
- def test_minimalloc_source_get_pool(sample_csv_path: Path) -> None:
98
- source = MinimallocSource(sample_csv_path)
99
- pool = source.get_pool()
100
- assert len(pool.allocations) == 5
101
-
102
-
103
- def test_minimalloc_source_get_allocation(sample_csv_path: Path) -> None:
104
- source = MinimallocSource(sample_csv_path)
105
- allocation = source.get_allocation()
106
- assert allocation.id == "0"
107
- assert allocation.size == 4
108
-
109
-
110
- def test_minimalloc_source_file_not_found() -> None:
111
- """Test that appropriate error is raised for missing file."""
112
- with pytest.raises(FileNotFoundError):
113
- MinimallocSource("/nonexistent/path.csv")
114
-
115
-
116
- def test_minimalloc_source_str_path(sample_csv_path: Path) -> None:
117
- """Test that string paths are accepted."""
118
- source = MinimallocSource(str(sample_csv_path))
119
- assert source.file_path == sample_csv_path
120
- allocations = source.get_allocations()
121
- assert len(allocations) == 5
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes