fastrectpack 0.1.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.
- fastrectpack-0.1.0/.clang-format +5 -0
- fastrectpack-0.1.0/.editorconfig +15 -0
- fastrectpack-0.1.0/.gitattributes +2 -0
- fastrectpack-0.1.0/.gitignore +9 -0
- fastrectpack-0.1.0/BENCHMARKS.md +92 -0
- fastrectpack-0.1.0/CHANGELOG.md +15 -0
- fastrectpack-0.1.0/CMakeLists.txt +23 -0
- fastrectpack-0.1.0/CONTRIBUTING.md +47 -0
- fastrectpack-0.1.0/LICENSE +201 -0
- fastrectpack-0.1.0/NOTICE +13 -0
- fastrectpack-0.1.0/PKG-INFO +164 -0
- fastrectpack-0.1.0/README.md +132 -0
- fastrectpack-0.1.0/SECURITY.md +19 -0
- fastrectpack-0.1.0/bench/cheap_wins.py +80 -0
- fastrectpack-0.1.0/bench/harness.py +148 -0
- fastrectpack-0.1.0/bench/plot_results.py +113 -0
- fastrectpack-0.1.0/bench/probe_cores.py +37 -0
- fastrectpack-0.1.0/bench/profile_original.py +53 -0
- fastrectpack-0.1.0/bench/run_baseline.py +42 -0
- fastrectpack-0.1.0/bench/run_bench.py +77 -0
- fastrectpack-0.1.0/bench/run_phase3.py +44 -0
- fastrectpack-0.1.0/bench/workloads.py +38 -0
- fastrectpack-0.1.0/docs/DESIGN.md +62 -0
- fastrectpack-0.1.0/pyproject.toml +70 -0
- fastrectpack-0.1.0/requirements-bench.txt +2 -0
- fastrectpack-0.1.0/src/fastrectpack/__init__.py +28 -0
- fastrectpack-0.1.0/src/fastrectpack/_maxrects.py +105 -0
- fastrectpack-0.1.0/src/fastrectpack/_native.cpp +244 -0
- fastrectpack-0.1.0/tests/helpers.py +90 -0
- fastrectpack-0.1.0/tests/test_edge_cases.py +125 -0
- fastrectpack-0.1.0/tests/test_metadata.py +19 -0
- fastrectpack-0.1.0/tests/test_parity.py +119 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
indent_style = space
|
|
9
|
+
indent_size = 4
|
|
10
|
+
|
|
11
|
+
[*.{yml,yaml,toml,json}]
|
|
12
|
+
indent_size = 2
|
|
13
|
+
|
|
14
|
+
[*.md]
|
|
15
|
+
trim_trailing_whitespace = false
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Benchmarks
|
|
2
|
+
|
|
3
|
+
## Reproduce
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pip install .
|
|
7
|
+
python bench/run_bench.py # writes results/bench.json and results/bench.md
|
|
8
|
+
python tools/summarize.py # writes results/summary.md from every results/bench*.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`--quick` runs five small cases in about a minute. On Windows the script restricts itself to logical CPUs 0-13 by default (`--cpus all` turns that off); see [Noise](#noise-on-this-machine).
|
|
12
|
+
|
|
13
|
+
## Environment
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Machine | Laptop, Intel Core i7-14700HX (20 cores, 28 threads; hybrid: fast and slow cores), 31.7 GB RAM |
|
|
18
|
+
| OS | Windows 11 Home, build 10.0.26100, "Balanced" power plan |
|
|
19
|
+
| Python | CPython 3.13.5 (Anaconda build, MSC v.1929, 64-bit) |
|
|
20
|
+
| Compiler | MSVC 19.44 (Visual Studio 2022), Release configuration, default optimisation flags |
|
|
21
|
+
| Original library | rectpack 0.2.2 |
|
|
22
|
+
| Build tools | pybind11 3.1.0, CMake 4.4.4, scikit-build-core (exact versions in `results/pip_freeze.txt`) |
|
|
23
|
+
| Date | 2026-11-10 |
|
|
24
|
+
|
|
25
|
+
## What is measured
|
|
26
|
+
|
|
27
|
+
- **Operation:** build a packer with `newPacker()` defaults (offline mode, best-bin-fit, `MaxRectsBssf`, sort by area, rotation on), add all rectangles and bins, call `pack()`, read `rect_list()`. The whole call is timed, including Python-side setup.
|
|
28
|
+
- **Three variants, same inputs:**
|
|
29
|
+
1. `original`: rectpack 0.2.2, untouched.
|
|
30
|
+
2. `python_fixes`: the original with two pure-Python changes and no C++ (`bench/cheap_wins.py`).
|
|
31
|
+
3. `native`: fastrectpack.
|
|
32
|
+
- **Workloads** (`bench/workloads.py`, fixed seeds, integer sizes):
|
|
33
|
+
- `random`: sides uniform in 10-100, bins 1000x1000.
|
|
34
|
+
- `cutting`: 20 typical sheet-part sizes with heavy repetition, bins 2440x1220. Uses many bins.
|
|
35
|
+
- `sprites`: sides drawn from 16 to 256 in the usual texture steps, bins 2048x2048.
|
|
36
|
+
- `skewed`: 5% large pieces (200-600), the rest small (5-60), bins 1500x1500.
|
|
37
|
+
- **Procedure:** the variants are run interleaved in rotating order, so background load affects them alike. One warm-up round for cases up to 1,000 rectangles. Rounds: 7 up to 1,000 rectangles, 5 at 2,000, 3 at 5,000. Garbage is collected before each timed call.
|
|
38
|
+
- **Statistic:** best (minimum) time per variant, and speedup = best original time / best variant time. Background load only ever adds time, so the minimum is the closest estimate of the code's own cost. The table also shows the range of per-round speedups from the main run.
|
|
39
|
+
- **Parity:** each run hashes the full output of all three variants. A mismatch fails the script.
|
|
40
|
+
|
|
41
|
+
## Results
|
|
42
|
+
|
|
43
|
+
Generated by `tools/summarize.py` into `results/summary.md`. "Best" is the minimum over every run recorded on this machine, which for the original is the least flattering choice for the speedup.
|
|
44
|
+
|
|
45
|
+
| Workload | Rectangles | Bins used | Original (best) | Python fixes (best) | C++ (best) | Speedup, Python fixes | Speedup, C++ | C++ speedup range across rounds of the main run | Output identical |
|
|
46
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
47
|
+
| random | 200 | 1 | 0.344 s | 0.114 s | 0.0032 s | 3.0x | 106x | 99x to 108x | yes |
|
|
48
|
+
| random | 500 | 2 | 0.944 s | 0.168 s | 0.0057 s | 5.6x | 164x | 69x to 165x | yes |
|
|
49
|
+
| random | 1,000 | 4 | 3.525 s | 0.473 s | 0.0144 s | 7.5x | 246x | 198x to 264x | yes |
|
|
50
|
+
| random | 2,000 | 7 | 7.703 s | 1.071 s | 0.0792 s | 7.2x | 97x | 115x to 219x | yes |
|
|
51
|
+
| random | 5,000 | 16 | 21.123 s | 3.738 s | 0.1364 s | 5.7x | 155x | 121x to 265x | yes |
|
|
52
|
+
| cutting | 500 | 27 | 0.036 s | 0.022 s | 0.0068 s | 1.7x | 5x | 5x to 5x | yes |
|
|
53
|
+
| cutting | 1,000 | 50 | 0.119 s | 0.066 s | 0.0221 s | 1.8x | 5x | 5x to 6x | yes |
|
|
54
|
+
| cutting | 2,000 | 100 | 0.415 s | 0.214 s | 0.0780 s | 1.9x | 5x | 5x to 6x | yes |
|
|
55
|
+
| sprites | 500 | 2 | 0.069 s | 0.032 s | 0.0029 s | 2.1x | 23x | 24x to 26x | yes |
|
|
56
|
+
| sprites | 1,000 | 3 | 0.487 s | 0.131 s | 0.0075 s | 3.7x | 65x | 67x to 70x | yes |
|
|
57
|
+
| sprites | 2,000 | 5 | 1.500 s | 0.322 s | 0.0177 s | 4.7x | 85x | 85x to 88x | yes |
|
|
58
|
+
| skewed | 500 | 3 | 0.684 s | 0.120 s | 0.0048 s | 5.7x | 141x | 135x to 144x | yes |
|
|
59
|
+
| skewed | 1,000 | 4 | 1.360 s | 0.243 s | 0.0102 s | 5.6x | 133x | 126x to 300x | yes |
|
|
60
|
+
| skewed | 2,000 | 11 | 2.197 s | 0.483 s | 0.0270 s | 4.5x | 82x | 78x to 187x | yes |
|
|
61
|
+
|
|
62
|
+
- C++ speedup: minimum 5.3x (cutting), maximum 246x (random, 1,000 rectangles), over 14 cases.
|
|
63
|
+
- Pure-Python fixes alone: 1.7x to 7.5x.
|
|
64
|
+
- All 14 outputs are identical across the three variants.
|
|
65
|
+
|
|
66
|
+
Where the two speedup columns disagree (for example random 2,000: 97x from best times, 115x to 219x within the main run), the lower figure is the safer one to quote. The disagreement comes from the noise described below.
|
|
67
|
+
|
|
68
|
+
## Where the time went in the original
|
|
69
|
+
|
|
70
|
+
`bench/profile_original.py` (cProfile, so read the shares, not the seconds):
|
|
71
|
+
|
|
72
|
+
| Function | random, 1,000 | skewed, 1,000 |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `_remove_duplicates` | 93.7% | 88.2% |
|
|
75
|
+
| `_select_position` | 3.6% | 7.6% |
|
|
76
|
+
| `_split` | 2.4% | 3.7% |
|
|
77
|
+
|
|
78
|
+
After every placement, `_remove_duplicates` compares every pair of free rectangles: 30.8 million `contains()` calls to place 1,000 rectangles in the random case, with a mean of 146.5 free rectangles per search.
|
|
79
|
+
|
|
80
|
+
## Where the speedup comes from
|
|
81
|
+
|
|
82
|
+
1. **Incremental duplicate removal (pure Python).** The free list never holds a rectangle that contains another once the step has run, so only pairs involving newly created pieces need checking. Measured alone: 1.0x to 7.1x (`results/phase3.json`).
|
|
83
|
+
2. **Single fitness evaluation (pure Python).** The original computes each candidate's fitness twice. Together with step 1: 1.7x to 7.5x.
|
|
84
|
+
3. **C++ port.** The same algorithm on plain structs instead of Python objects.
|
|
85
|
+
|
|
86
|
+
## Noise on this machine
|
|
87
|
+
|
|
88
|
+
- **Hybrid CPU.** The same fixed loop takes 378-414 ms on logical CPUs 0-13 and 577-1,501 ms on CPUs 14-27 (`bench/probe_cores.py`). An unrestricted process drifts between them: the first baseline showed identical runs taking 21 s and 57 s. The benchmark therefore restricts itself to CPUs 0-13.
|
|
89
|
+
- **Slow phases.** Even on the fast cores, the machine drops into stretches where everything runs about 2.3x slower. Other background software was running during the first two full runs, but the third run, with it closed and the laptop on mains power, showed the same slow phases, so it is not the main cause. Thermal or power throttling is the likely explanation; I did not confirm it. Because variants are interleaved, a slow phase moves all three together: best-time speedup ratios agreed to roughly 10% between full runs, while absolute seconds did not.
|
|
90
|
+
- **Consequence.** Treat the speedup ratios as the result. Treat the absolute seconds as approximate. A re-run on a desktop, or a laptop that does not throttle, would firm them up.
|
|
91
|
+
|
|
92
|
+
Raw files: `results/bench.json` (main run, the third, with other software closed), `results/bench_run2.json` (second run), `results/bench_run1_single_core_pin.json` (first run, pinned to one core), `results/baseline.json` (original only), `results/phase3.json` (pure-Python steps). `results/baseline_unpinned_UNRELIABLE.json` is kept as evidence of the core-drift problem; its best times are included in the "best" columns only because lower original times make the speedup claim more conservative.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - not yet released
|
|
6
|
+
|
|
7
|
+
First version.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- C++17 (pybind11) implementations of `MaxRectsBl`, `MaxRectsBssf`, `MaxRectsBaf` and `MaxRectsBlsf`, exposed as subclasses of the originals. Output is identical to `rectpack` 0.2.2.
|
|
11
|
+
- Fast path for plain Python `int` sizes up to 2^30. Any other input (floats, `Decimal`, numpy integers, `bool`, zero or negative sizes, larger integers) hands the object over to the original Python code for the rest of its life.
|
|
12
|
+
- `fastrectpack.newPacker`, same signature as `rectpack.newPacker`, defaulting to the accelerated algorithm. Everything else `rectpack` exports is re-exported unchanged.
|
|
13
|
+
- Test suite of 474 tests: step-by-step parity, edge cases, property-based tests, all 200 packer configurations that use a MaxRects algorithm.
|
|
14
|
+
- Benchmarks (`bench/`), raw results (`results/`) and a chart (`docs/`).
|
|
15
|
+
- CI: wheels for CPython 3.9 to 3.13 on Linux x86_64, Windows x86_64 and macOS arm64; AddressSanitizer and UndefinedBehaviorSanitizer on Linux.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.15...3.31)
|
|
2
|
+
project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
7
|
+
|
|
8
|
+
find_package(pybind11 CONFIG REQUIRED)
|
|
9
|
+
|
|
10
|
+
pybind11_add_module(_native MODULE src/fastrectpack/_native.cpp)
|
|
11
|
+
|
|
12
|
+
# Optional sanitizer build for the correctness phase: pip install . -C cmake.define.FASTRECTPACK_SANITIZE=address
|
|
13
|
+
set(FASTRECTPACK_SANITIZE "" CACHE STRING "Sanitizers to enable (address, undefined, thread)")
|
|
14
|
+
if(FASTRECTPACK_SANITIZE)
|
|
15
|
+
if(MSVC)
|
|
16
|
+
target_compile_options(_native PRIVATE /fsanitize=${FASTRECTPACK_SANITIZE} /Zi)
|
|
17
|
+
else()
|
|
18
|
+
target_compile_options(_native PRIVATE -fsanitize=${FASTRECTPACK_SANITIZE} -fno-omit-frame-pointer -g)
|
|
19
|
+
target_link_options(_native PRIVATE -fsanitize=${FASTRECTPACK_SANITIZE})
|
|
20
|
+
endif()
|
|
21
|
+
endif()
|
|
22
|
+
|
|
23
|
+
install(TARGETS _native LIBRARY DESTINATION fastrectpack)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for looking. This project has one rule that matters more than the others: **fastrectpack must give exactly the same answers as the original `rectpack`.** Speed is the point, but only if the output is identical.
|
|
4
|
+
|
|
5
|
+
## Set up
|
|
6
|
+
|
|
7
|
+
You need Python 3.9+, CMake 3.15+ and a C++17 compiler (MSVC 2019+, GCC 9+ or Clang 10+).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
python -m venv .venv
|
|
11
|
+
. .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
12
|
+
pip install ".[test]" numpy
|
|
13
|
+
pytest -q
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Re-run `pip install .` after changing `src/fastrectpack/_native.cpp`; the extension is rebuilt on install.
|
|
17
|
+
|
|
18
|
+
On Windows from Git Bash, set `CMAKE_GENERATOR="Visual Studio 17 2022"` first. A "Developer PowerShell for VS" prompt should not need it, but that has not been tested.
|
|
19
|
+
|
|
20
|
+
## Before you open a pull request
|
|
21
|
+
|
|
22
|
+
1. **Parity first.** The tests compare every placement and the full free-rectangle list against the original, step by step. If you change behaviour on purpose, say why in the PR; if a change alters output, the tests are meant to fail.
|
|
23
|
+
2. **Add a test** for what you changed. For anything touching the C++, extend `tests/test_parity.py` or `tests/test_edge_cases.py`.
|
|
24
|
+
3. **Claiming a speedup?** Include numbers from `python bench/run_bench.py`, with the hardware, and say how many runs they came from. Please quote the best-of ratio and show the spread. See `BENCHMARKS.md` for why.
|
|
25
|
+
4. **Style.** `ruff check .` for Python and `clang-format -i src/fastrectpack/_native.cpp` for C++. CI checks both.
|
|
26
|
+
5. **Changelog.** Add a line to `CHANGELOG.md`.
|
|
27
|
+
|
|
28
|
+
If a substantial part of a change was written with an AI assistant, please say so in the PR description.
|
|
29
|
+
|
|
30
|
+
## Things worth knowing
|
|
31
|
+
|
|
32
|
+
- Quirks inherited from rectpack are **reproduced on purpose** so that output matches (see "Behaviour inherited from rectpack" in the README). Please do not fix them here; they belong upstream.
|
|
33
|
+
- The fast path only handles plain Python `int` sizes up to 2^30. Anything else falls back to the original code. Adding a float fast path is welcome, but must keep value types identical (`0` versus `0.0`), which is harder than it looks. See `docs/DESIGN.md`.
|
|
34
|
+
- Moving the bin-selection loop (`PackerBBF` and friends) into C++ is the most valuable open improvement. It would lift workloads that spread rectangles over many bins above the current ~5x.
|
|
35
|
+
|
|
36
|
+
## Checks you can run
|
|
37
|
+
|
|
38
|
+
| What | Command |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Tests | `pytest -q` |
|
|
41
|
+
| Quick benchmark (about a minute) | `python bench/run_bench.py --quick` |
|
|
42
|
+
| Mutation check (breaks the C++ on purpose, expects the tests to notice) | `python tools/mutation_check.py` |
|
|
43
|
+
| AddressSanitizer + UBSan (Linux) | `bash tools/sanitize_linux.sh` |
|
|
44
|
+
|
|
45
|
+
## Reporting problems
|
|
46
|
+
|
|
47
|
+
Use the issue templates. For a performance report, include the workload shape (rectangle count and size range, bin size and count) and the packer settings. For anything that looks like a memory-safety problem, see [SECURITY.md](SECURITY.md) instead of opening a public issue.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright {yyyy} {name of copyright owner}
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
fastrectpack
|
|
2
|
+
Copyright 2026 Ramcharan
|
|
3
|
+
|
|
4
|
+
This product is a derivative work of rectpack (https://github.com/secnot/rectpack),
|
|
5
|
+
licensed under the Apache License, Version 2.0, by its author SecNot.
|
|
6
|
+
|
|
7
|
+
Changes made: the MaxRects packing algorithms (MaxRectsBl, MaxRectsBssf, MaxRectsBaf,
|
|
8
|
+
MaxRectsBlsf) were re-implemented in C++17 for plain Python int sizes and are exposed
|
|
9
|
+
through classes that subclass the originals. All other behaviour and all other algorithms
|
|
10
|
+
are the unmodified rectpack code, which is installed as a dependency and re-exported.
|
|
11
|
+
|
|
12
|
+
The MaxRects family itself is described in: Jukka Jylanki, "A Thousand Ways to Pack the
|
|
13
|
+
Bin - A Practical Approach to Two-Dimensional Rectangle Bin Packing" (2010).
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastrectpack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drop-in C++ accelerator for rectpack's MaxRects 2D packing algorithms, with identical output
|
|
5
|
+
Keywords: rectpack,bin packing,rectangle packing,2d packing,nesting,pybind11,performance
|
|
6
|
+
Author: Ramcharan
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: C++
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Project-URL: Homepage, https://github.com/p-ramcharan-dev/fastrectpack
|
|
22
|
+
Project-URL: Source, https://github.com/p-ramcharan-dev/fastrectpack
|
|
23
|
+
Project-URL: Issues, https://github.com/p-ramcharan-dev/fastrectpack/issues
|
|
24
|
+
Project-URL: Benchmarks, https://github.com/p-ramcharan-dev/fastrectpack/blob/main/BENCHMARKS.md
|
|
25
|
+
Project-URL: Original library, https://github.com/secnot/rectpack
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: rectpack==0.2.2
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest; extra == "test"
|
|
30
|
+
Requires-Dist: hypothesis; extra == "test"
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# fastrectpack
|
|
34
|
+
|
|
35
|
+
[](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/ci.yml)
|
|
36
|
+
[](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/wheels.yml)
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
A drop-in accelerator for [rectpack](https://github.com/secnot/rectpack), the pure-Python 2D rectangle packing library. The MaxRects packing algorithms are re-implemented in C++17 and produce **exactly the same packings** as the original, 5x to 246x faster depending on the workload.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# before
|
|
44
|
+
from rectpack import newPacker
|
|
45
|
+
# after: nothing else changes
|
|
46
|
+
from fastrectpack import newPacker
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Headline numbers
|
|
50
|
+
|
|
51
|
+
Default `newPacker()` settings. Times are the best recorded run; full method and all 14 cases are in [BENCHMARKS.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/BENCHMARKS.md).
|
|
52
|
+
|
|
53
|
+
| Workload | Rectangles | Original rectpack | Pure-Python fixes only | fastrectpack (C++) | Speedup |
|
|
54
|
+
|---|---|---|---|---|---|
|
|
55
|
+
| random sizes | 1,000 | 3.525 s | 0.473 s | 0.0144 s | 246x |
|
|
56
|
+
| random sizes | 5,000 | 21.123 s | 3.738 s | 0.1364 s | 155x |
|
|
57
|
+
| few large, many small | 1,000 | 1.360 s | 0.243 s | 0.0102 s | 133x |
|
|
58
|
+
| sprite sizes | 2,000 | 1.500 s | 0.322 s | 0.0177 s | 85x |
|
|
59
|
+
| sheet cutting, 100 bins | 2,000 | 0.415 s | 0.214 s | 0.0780 s | 5x |
|
|
60
|
+
|
|
61
|
+
Two things to read from this table:
|
|
62
|
+
|
|
63
|
+
- **Part of the win needs no C++.** A better algorithm in plain Python gives 1.7x to 7.5x on its own (the "Pure-Python fixes only" column). The C++ port supplies the rest.
|
|
64
|
+
- **The gain depends on the workload.** Jobs that spread rectangles over many bins gain about 5x, because the bin-selection loop is still Python. See "What is not faster" below.
|
|
65
|
+
|
|
66
|
+
The benchmark machine is a laptop that drops into slow phases (probably thermal throttling), so absolute seconds vary by up to about 2.3x. Best-time speedup ratios agreed to roughly 10% between full runs.
|
|
67
|
+
|
|
68
|
+
<p align="center">
|
|
69
|
+
<picture>
|
|
70
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/p-ramcharan-dev/fastrectpack/main/docs/benchmarks-dark.png">
|
|
71
|
+
<img alt="Dumbbell chart of the speedup over the original rectpack for 14 workloads, log scale. Pure-Python fixes alone give 1.7x to 7.5x; the C++ version gives 5x to 246x, with output identical in every case. Jobs that spread rectangles over many bins gain about 5x. The same numbers are in the tables in this README and in BENCHMARKS.md." src="https://raw.githubusercontent.com/p-ramcharan-dev/fastrectpack/main/docs/benchmarks-light.png" width="760">
|
|
72
|
+
</picture>
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
pip install fastrectpack
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Prebuilt wheels cover CPython 3.9 to 3.13 on Linux x86_64 (manylinux), Windows x86_64 and macOS arm64 (Apple silicon). Other platforms (Linux ARM, Intel Macs, Alpine/musl) and Python 3.14 are untested and build from the source distribution, which needs CMake 3.15+ and a C++17 compiler (MSVC 2019+, GCC 9+ or Clang 10+). From a checkout: `pip install .`. `rectpack==0.2.2` is installed as a dependency.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
Identical to rectpack:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from fastrectpack import newPacker
|
|
89
|
+
|
|
90
|
+
packer = newPacker()
|
|
91
|
+
for width, height in [(100, 30), (40, 60), (30, 30), (70, 70), (100, 50), (30, 30)]:
|
|
92
|
+
packer.add_rect(width, height)
|
|
93
|
+
packer.add_bin(300, 450)
|
|
94
|
+
packer.pack()
|
|
95
|
+
|
|
96
|
+
for bin_index, x, y, w, h, rid in packer.rect_list():
|
|
97
|
+
print(bin_index, x, y, w, h)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`fastrectpack` re-exports everything rectpack exports. `MaxRectsBl`, `MaxRectsBssf`, `MaxRectsBaf` and `MaxRectsBlsf` are replaced by accelerated subclasses of the originals; everything else is the original object.
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
The four MaxRects classes subclass the originals and run the algorithm in a small C++17 core while every size is a plain Python `int`. Anything else falls back, for good, to the original Python code for that object.
|
|
105
|
+
|
|
106
|
+
Profiling the original showed about 90% of its time in one step that compares every pair of free rectangles after each placement. The port compares only the pairs that involve newly created rectangles, which is valid because the list left by the previous step contains no rectangle that contains another. [docs/DESIGN.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/docs/DESIGN.md) covers the design, exactly what "identical" means, and the known gaps.
|
|
107
|
+
|
|
108
|
+
## What is accelerated
|
|
109
|
+
|
|
110
|
+
| | Accelerated |
|
|
111
|
+
|---|---|
|
|
112
|
+
| MaxRects algorithms (Bl, Bssf, Baf, Blsf), with or without rotation | Yes |
|
|
113
|
+
| All bin heuristics (BNF, BFF, BBF, Global), online and offline modes, all sort orders | Yes, when used with a MaxRects algorithm |
|
|
114
|
+
| Sizes given as plain Python `int`, up to 2^30 | Yes |
|
|
115
|
+
| Sizes given as `float`, `Decimal`, numpy integers or `bool` | No: handled by the original code, same result, original speed |
|
|
116
|
+
| Skyline and Guillotine algorithms | No: unchanged original code |
|
|
117
|
+
|
|
118
|
+
When an accelerated object meets an input outside the fast path, it hands its state to the original Python implementation and continues there, so results and error messages stay the same.
|
|
119
|
+
|
|
120
|
+
## What is not faster
|
|
121
|
+
|
|
122
|
+
- **Many-bin workloads.** With 100 open bins, most of the time goes to the Python loop that asks every bin how well a rectangle fits. That loop was left in Python, so the gain there is about 5x.
|
|
123
|
+
- **Non-integer sizes.** Floats and Decimals run at the original speed.
|
|
124
|
+
- **Skyline and Guillotine.** Not ported.
|
|
125
|
+
|
|
126
|
+
## How correctness was checked
|
|
127
|
+
|
|
128
|
+
- **Exact parity.** After every single placement, the tests compare the fitness value, the placement and the complete list of free rectangles (including its order) against the original. 474 tests, including two Hypothesis property tests (300 and 150 generated cases) and all 200 packer configurations that use a MaxRects algorithm.
|
|
129
|
+
- **Edge cases.** Floats, Decimals, NaN, infinity, zero and negative sizes, booleans, strings, `None`, numpy integers, integers beyond the native range, exact fits, pieces larger than the bin. Results and exceptions match the original.
|
|
130
|
+
- **Benchmark outputs.** Every benchmark run hashes the full result of all three variants; all 14 cases are identical.
|
|
131
|
+
- **Sanitizers.** The suite passes under AddressSanitizer and UndefinedBehaviorSanitizer, both locally (GCC 15.2 in WSL) and on a GitHub Actions Ubuntu runner.
|
|
132
|
+
- **Mutation check.** Six deliberate bugs were injected into the C++. The tests caught five; the sixth (treating edge-touching rectangles as overlapping) only adds free rectangles that the next step deletes, so the output cannot change. I reasoned that through rather than proving it formally.
|
|
133
|
+
- **Platforms and Python versions.** In GitHub Actions the test suite ran inside every built wheel: Python 3.9, 3.10, 3.11, 3.12 and 3.13 on Linux x86_64, Windows x86_64 and macOS arm64. Locally it also passes on 3.13 and 3.11 on Windows.
|
|
134
|
+
|
|
135
|
+
Run them yourself:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
pip install ".[test]"
|
|
139
|
+
pytest
|
|
140
|
+
python bench/run_bench.py --quick # about a minute
|
|
141
|
+
python bench/run_bench.py # full benchmark, several minutes
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Behaviour inherited from rectpack
|
|
145
|
+
|
|
146
|
+
These were found while porting. They are reproduced on purpose, not fixed, so that results match ([`research/original_bugs.py`](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/research/original_bugs.py) demonstrates each):
|
|
147
|
+
|
|
148
|
+
- `validate_packing()` never compares the last rectangle with the others, so an overlap involving it goes undetected.
|
|
149
|
+
- `MaxRectsBl` documents a smallest-x tie-break but takes the first candidate.
|
|
150
|
+
- With the BBF bin heuristic, online `add_rect` returns `True`/`False` instead of the rectangle.
|
|
151
|
+
|
|
152
|
+
## Limitations
|
|
153
|
+
|
|
154
|
+
- Speed was measured on one Windows laptop only. Correctness is checked in CI on Linux, Windows and macOS (arm64), but speed on those systems has not been measured.
|
|
155
|
+
- The package subclasses rectpack internals and pins `rectpack==0.2.2`. A new rectpack release would need re-checking.
|
|
156
|
+
- An unpickled or deep-copied packer continues on the original Python code path: correct, but not accelerated.
|
|
157
|
+
|
|
158
|
+
## Contributing and security
|
|
159
|
+
|
|
160
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/CONTRIBUTING.md). The most useful report is a real workload where it is still slow. Please report security problems privately, as described in [SECURITY.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/SECURITY.md).
|
|
161
|
+
|
|
162
|
+
## Credits and licence
|
|
163
|
+
|
|
164
|
+
Apache-2.0, the same licence as rectpack, of which this is a derivative work. rectpack is by SecNot. The MaxRects algorithms come from Jukka Jylänki, "A Thousand Ways to Pack the Bin" (2010). See [NOTICE](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/NOTICE).
|