Skip to content

Commit 3c49c2a

Browse files
Copilotshauneccles
andcommitted
Add .gitignore for compiled extensions and create comprehensive NANOBIND_PLAN.md
Co-authored-by: shauneccles <21007065+shauneccles@users.noreply.github.com>
1 parent 4fbf9ae commit 3c49c2a

3 files changed

Lines changed: 375 additions & 1 deletion

File tree

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ docs/_build
1414
tags
1515
.vscode/
1616

17-
samplerate/_src.py
17+
samplerate/_src.py
18+
19+
# Compiled extensions
20+
*.so
21+
*.pyd
22+
*.dll

NANOBIND_PLAN.md

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
# Nanobind Migration Plan
2+
3+
## Overview
4+
This document outlines the plan for migrating the python-samplerate-ledfx bindings from pybind11 to nanobind 2.9.2. The migration will create a new nanobind implementation that can be imported as `samplerate-nb` while maintaining the existing pybind11 bindings for regression testing.
5+
6+
## Goals
7+
1. Create a drop-in replacement for the existing pybind11 bindings
8+
2. Maintain API compatibility (seamless consumer experience)
9+
3. Leverage nanobind's performance improvements (smaller binaries, faster compilation)
10+
4. Enable comprehensive regression testing against pybind11 baseline
11+
5. Development name: `samplerate-nb`, internal module: `samplerate` for easy migration
12+
13+
## Key Differences: pybind11 vs nanobind 2.9.2
14+
15+
### Philosophy
16+
- **pybind11**: Broad feature coverage, aims to bind all of C++
17+
- **nanobind**: Focused on common use cases, optimized for efficiency and simplicity
18+
19+
### Performance Benefits
20+
- **Binary size**: ~5x smaller
21+
- **Compile time**: ~4x faster
22+
- **Runtime overhead**: ~10x reduction
23+
- **Memory footprint**: ~2.3x reduction per wrapped object
24+
25+
### Technical Requirements
26+
- **Minimum C++**: C++17 (vs C++14 for pybind11)
27+
- **Minimum Python**: 3.8+
28+
- **CMake**: 3.15+
29+
30+
### API Changes
31+
- Very similar syntax to pybind11 for most use cases
32+
- Some fringe features removed/changed
33+
- Better stub generation with NDArray types
34+
- Improved multi-threading support with localized locking
35+
36+
## Migration Phases
37+
38+
### Phase 1: Infrastructure Setup ✅
39+
**Status**: Ready to begin
40+
41+
**Tasks**:
42+
1. ✅ Explore repository structure
43+
2. ✅ Understand existing pybind11 bindings
44+
3. ✅ Build and test baseline (87 tests passing)
45+
4. ✅ Research nanobind 2.9.2 features
46+
5. Create NANOBIND_PLAN.md document
47+
6. Update `.gitignore` for compiled extensions
48+
49+
**Deliverables**:
50+
- Working baseline with all tests passing
51+
- Comprehensive understanding of codebase
52+
- Migration plan document
53+
54+
---
55+
56+
### Phase 2: Build System Configuration
57+
**Status**: Not started
58+
59+
**Tasks**:
60+
1. Update `external/CMakeLists.txt` to fetch nanobind
61+
2. Create new `CMakeLists_nb.txt` for nanobind module
62+
3. Update `setup.py` to support dual builds (both pybind11 and nanobind)
63+
4. Configure nanobind module to output as `samplerate` (internal) but be importable as `samplerate-nb`
64+
5. Test basic build infrastructure
65+
66+
**Deliverables**:
67+
- CMake configuration for nanobind
68+
- Build system supporting both binding libraries
69+
- Empty nanobind module that compiles successfully
70+
71+
**Technical Notes**:
72+
- Use FetchContent to get nanobind (similar to pybind11)
73+
- nanobind_add_module() replaces pybind11_add_module()
74+
- Separate build targets: `python-samplerate` (pybind11) and `python-samplerate-nb` (nanobind)
75+
76+
---
77+
78+
### Phase 3: Core Bindings Implementation
79+
**Status**: Not started
80+
81+
**Tasks**:
82+
1. Create `src/samplerate_nb.cpp` (new file, do not modify existing)
83+
2. Port header includes from pybind11 to nanobind
84+
3. Implement basic module structure
85+
4. Port `ConverterType` enum
86+
5. Port `ResamplingException` custom exception
87+
6. Implement `get_converter_type()` helper function
88+
7. Implement `error_handler()` function
89+
90+
**Deliverables**:
91+
- Working module skeleton with basic types
92+
- Exception handling matching pybind11 behavior
93+
- Helper functions operational
94+
95+
**API Mapping**:
96+
```cpp
97+
pybind11 → nanobind
98+
#include <pybind11/pybind11.h> → #include <nanobind/nanobind.h>
99+
#include <pybind11/numpy.h> → #include <nanobind/ndarray.h>
100+
#include <pybind11/stl.h> → #include <nanobind/stl/string.h>
101+
#include <pybind11/functional.h> → #include <nanobind/stl/function.h>
102+
103+
namespace py = pybind11; → namespace nb = nanobind;
104+
PYBIND11_MODULE(...) → NB_MODULE(...)
105+
py::array_t<float> → nb::ndarray<nb::numpy, float>
106+
py::gil_scoped_release → nb::gil_scoped_release
107+
py::gil_scoped_acquire → nb::gil_scoped_acquire
108+
```
109+
110+
---
111+
112+
### Phase 4: Simple API Implementation
113+
**Status**: Not started
114+
115+
**Tasks**:
116+
1. Port `resample()` function
117+
2. Adapt NumPy array handling for nanobind
118+
3. Handle GIL release/acquire
119+
4. Implement verbose parameter
120+
5. Write test comparing against pybind11 `resample()`
121+
122+
**Deliverables**:
123+
- Working `resample()` function
124+
- Tests passing for Simple API
125+
- Performance comparison data
126+
127+
**Testing Strategy**:
128+
```python
129+
import samplerate # pybind11 version
130+
import samplerate_nb # nanobind version
131+
132+
# Regression test
133+
output_pb = samplerate.resample(input_data, ratio, converter)
134+
output_nb = samplerate_nb.resample(input_data, ratio, converter)
135+
assert np.allclose(output_pb, output_nb)
136+
```
137+
138+
---
139+
140+
### Phase 5: Full API Implementation
141+
**Status**: Not started
142+
143+
**Tasks**:
144+
1. Port `Resampler` class
145+
2. Implement constructor, copy constructor, move constructor
146+
3. Port `process()` method with NumPy array handling
147+
4. Implement `set_ratio()`, `reset()`, `clone()` methods
148+
5. Expose readonly attributes
149+
6. Write tests for all Resampler functionality
150+
151+
**Deliverables**:
152+
- Fully functional `Resampler` class
153+
- All methods tested against pybind11 baseline
154+
- Clone/copy semantics verified
155+
156+
**Key Considerations**:
157+
- Ensure state management matches pybind11 behavior
158+
- Verify destructor behavior (src_delete handles nullptr)
159+
- Test multi-channel support (1D vs 2D arrays)
160+
161+
---
162+
163+
### Phase 6: Callback API Implementation
164+
**Status**: Not started
165+
166+
**Tasks**:
167+
1. Port `CallbackResampler` class
168+
2. Implement callback function handling
169+
3. Port context manager support (`__enter__`, `__exit__`)
170+
4. Implement `read()` method
171+
5. Handle callback error propagation
172+
6. Port callback wrapper function (`the_callback_func`)
173+
7. Write comprehensive callback tests
174+
175+
**Deliverables**:
176+
- Fully functional `CallbackResampler` class
177+
- Context manager support working
178+
- Callback error handling tested
179+
- Multi-channel callback support verified
180+
181+
**Key Considerations**:
182+
- Callback GIL management is critical (acquire when calling Python)
183+
- Error propagation from C callback to Python needs careful handling
184+
- Context manager should properly destroy state
185+
186+
---
187+
188+
### Phase 7: Module Organization
189+
**Status**: Not started
190+
191+
**Tasks**:
192+
1. Port submodule structure (`exceptions`, `converters`, `_internals`)
193+
2. Implement convenience imports
194+
3. Add version attributes (`__version__`, `__libsamplerate_version__`)
195+
4. Verify all imports work as expected
196+
5. Test import patterns used in tests
197+
198+
**Deliverables**:
199+
- Module organization matching pybind11
200+
- All convenience imports working
201+
- Version information accessible
202+
203+
---
204+
205+
### Phase 8: Comprehensive Testing
206+
**Status**: Not started
207+
208+
**Tasks**:
209+
1. Run all existing tests against nanobind implementation
210+
2. Create regression test suite comparing pybind11 vs nanobind
211+
3. Test all converter types (0-4, strings, enum)
212+
4. Test 1D and 2D array inputs
213+
5. Test edge cases (empty arrays, large ratios, etc.)
214+
6. Verify exception handling
215+
7. Test clone operations
216+
8. Performance benchmarking
217+
218+
**Deliverables**:
219+
- All 87+ tests passing for nanobind
220+
- Regression test suite showing identical behavior
221+
- Performance comparison report
222+
- Documentation of any behavioral differences
223+
224+
**Test Categories**:
225+
- Simple API: `test_simple()`, `test_match()`
226+
- Full API: `test_process()`, `test_Resampler_clone()`
227+
- Callback API: `test_callback()`, `test_callback_with()`, `test_CallbackResampler_clone()`
228+
- Type handling: `test_converter_type()`
229+
- Exceptions: tests in `test_exception.py`
230+
- Threading: `test_threading_performance.py`
231+
- Async: `test_asyncio_performance.py`
232+
233+
---
234+
235+
### Phase 9: Performance Analysis
236+
**Status**: Not started
237+
238+
**Tasks**:
239+
1. Compare compile times (pybind11 vs nanobind)
240+
2. Compare binary sizes
241+
3. Benchmark runtime performance
242+
4. Measure memory usage
243+
5. Test multi-threading performance
244+
6. Document findings
245+
246+
**Deliverables**:
247+
- Performance comparison report
248+
- Binary size comparison
249+
- Runtime benchmark results
250+
- Threading scalability data
251+
252+
**Metrics to Track**:
253+
- Compilation time (cold and warm builds)
254+
- Binary size (`.so` file)
255+
- Function call overhead
256+
- Memory per wrapped object
257+
- Multi-threaded scaling
258+
259+
---
260+
261+
### Phase 10: Documentation & Integration
262+
**Status**: Not started
263+
264+
**Tasks**:
265+
1. Document build process for nanobind variant
266+
2. Update README with nanobind information
267+
3. Document performance improvements
268+
4. Create migration guide for consumers
269+
5. Document import changes (samplerate-nb)
270+
6. Add CI/CD configuration for dual builds (if needed)
271+
272+
**Deliverables**:
273+
- Updated documentation
274+
- Migration guide for end users
275+
- CI/CD configuration
276+
- Final validation
277+
278+
---
279+
280+
## Technical Implementation Notes
281+
282+
### NumPy Array Handling
283+
284+
**pybind11**:
285+
```cpp
286+
py::array_t<float, py::array::c_style | py::array::forcecast> input
287+
py::buffer_info inbuf = input.request();
288+
```
289+
290+
**nanobind**:
291+
```cpp
292+
nb::ndarray<nb::numpy, float, nb::c_contig> input
293+
// Direct shape/data access without buffer_info
294+
size_t rows = input.shape(0);
295+
float* data = input.data();
296+
```
297+
298+
### GIL Management
299+
Both libraries support similar GIL scoped release/acquire:
300+
```cpp
301+
// Release GIL for C++ operations
302+
{
303+
nb::gil_scoped_release release;
304+
// ... call libsamplerate ...
305+
}
306+
307+
// Acquire GIL for Python calls
308+
{
309+
nb::gil_scoped_acquire acquire;
310+
// ... call Python callback ...
311+
}
312+
```
313+
314+
### Exception Handling
315+
nanobind uses same approach but with `nb::` namespace:
316+
```cpp
317+
nb::register_exception<ResamplingException>(m_exceptions, "ResamplingError", PyExc_RuntimeError);
318+
```
319+
320+
### Build Configuration
321+
```cmake
322+
# Fetch nanobind
323+
FetchContent_Declare(
324+
nanobind
325+
GIT_REPOSITORY https://github.com/wjakob/nanobind
326+
GIT_TAG v2.9.2
327+
)
328+
FetchContent_MakeAvailable(nanobind)
329+
330+
# Create module
331+
nanobind_add_module(python-samplerate-nb src/samplerate_nb.cpp)
332+
```
333+
334+
## Success Criteria
335+
336+
1.**Functional Parity**: All 87+ tests pass with nanobind implementation
337+
2.**API Compatibility**: Drop-in replacement (importable as samplerate-nb)
338+
3.**Regression Testing**: Identical behavior to pybind11 version
339+
4.**Performance**: Binary size reduction, faster compile times
340+
5.**Documentation**: Clear migration path for consumers
341+
342+
## Risk Mitigation
343+
344+
1. **Preserve pybind11 bindings**: Keep original for regression testing
345+
2. **Incremental implementation**: Test each component before moving on
346+
3. **Comprehensive testing**: Use existing test suite as baseline
347+
4. **Performance validation**: Benchmark at each phase
348+
5. **Documentation**: Record all differences and workarounds
349+
350+
## Timeline Estimate
351+
352+
- **Phase 1**: Infrastructure Setup - ✅ Complete
353+
- **Phase 2**: Build System - 1 hour
354+
- **Phase 3**: Core Bindings - 2 hours
355+
- **Phase 4**: Simple API - 1 hour
356+
- **Phase 5**: Full API - 2 hours
357+
- **Phase 6**: Callback API - 3 hours
358+
- **Phase 7**: Module Organization - 1 hour
359+
- **Phase 8**: Testing - 2 hours
360+
- **Phase 9**: Performance Analysis - 1 hour
361+
- **Phase 10**: Documentation - 1 hour
362+
363+
**Total Estimated Time**: ~14 hours
364+
365+
## Current Status
366+
367+
**Phase 1 Complete**: Infrastructure setup done, baseline established with 87 tests passing.
368+
369+
**Next Steps**: Begin Phase 2 - Build System Configuration
-1.73 MB
Binary file not shown.

0 commit comments

Comments
 (0)