Skip to content

Commit d576646

Browse files
committed
Update readme
1 parent c2bdca4 commit d576646

3 files changed

Lines changed: 265 additions & 110 deletions

File tree

.github/workflows/test-rust.yml

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ jobs:
9090
assert bson.has_rust(), 'Rust extension should be available'
9191
"
9292
93-
- name: Test with C extension (default)
93+
- name: Smoke test - C extension (default)
9494
run: |
9595
python -c "
9696
import bson
@@ -99,10 +99,10 @@ jobs:
9999
encoded = bson.encode(data)
100100
decoded = bson.decode(encoded)
101101
assert decoded == data, 'C extension encode/decode failed'
102-
print('C extension works')
102+
print('C extension smoke test passed')
103103
"
104104
105-
- name: Test with Rust extension
105+
- name: Smoke test - Rust extension
106106
env:
107107
PYMONGO_USE_RUST: "1"
108108
run: |
@@ -113,9 +113,19 @@ jobs:
113113
encoded = bson.encode(data)
114114
decoded = bson.decode(encoded)
115115
assert decoded == data, 'Rust extension encode/decode failed'
116-
print('Rust extension works')
116+
print('Rust extension smoke test passed')
117117
"
118118
119+
- name: Run BSON test suite with C extension (baseline)
120+
run: |
121+
python -m unittest test.test_bson.TestBSON -v
122+
123+
- name: Run BSON test suite with Rust extension
124+
env:
125+
PYMONGO_USE_RUST: "1"
126+
run: |
127+
python -m unittest test.test_bson.TestBSON -v
128+
119129
- name: Test cross-compatibility (C → Rust)
120130
run: |
121131
python -c "
@@ -149,41 +159,9 @@ jobs:
149159
assert bson_rust.get_bson_implementation() == 'rust'
150160
rust_decoded = bson_rust.decode(c_encoded)
151161
assert rust_decoded == data, 'Cross-compatibility failed'
152-
print('C to Rust cross-compatibility works')
162+
print('C to Rust cross-compatibility works')
153163
"
154164
155165
- name: Run performance benchmark
156166
run: |
157167
FASTBENCH=1 python test/performance/perf_test.py TestRustSimpleIntEncodingC TestRustSimpleIntEncodingRust TestRustMixedTypesEncodingC TestRustMixedTypesEncodingRust -v
158-
159-
- name: Test Rust extension with complex types
160-
env:
161-
PYMONGO_USE_RUST: "1"
162-
run: |
163-
python -c "
164-
import bson
165-
from bson import ObjectId, Decimal128
166-
from datetime import datetime
167-
168-
# Test that ObjectId works
169-
data = {'_id': ObjectId()}
170-
encoded = bson.encode(data)
171-
decoded = bson.decode(encoded)
172-
assert decoded['_id'] == data['_id']
173-
print('ObjectId encoding/decoding works')
174-
175-
# Test that DateTime works
176-
data = {'timestamp': datetime.now()}
177-
encoded = bson.encode(data)
178-
decoded = bson.decode(encoded)
179-
# Datetime comparison with microsecond precision
180-
assert abs((decoded['timestamp'] - data['timestamp']).total_seconds()) < 0.001
181-
print('DateTime encoding/decoding works')
182-
183-
# Test Decimal128
184-
data = {'price': Decimal128('123.45')}
185-
encoded = bson.encode(data)
186-
decoded = bson.decode(encoded)
187-
assert str(decoded['price']) == '123.45'
188-
print('Decimal128 encoding/decoding works')
189-
"

README.md

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,16 +278,100 @@ python your_script.py
278278
### Performance Results
279279

280280
**Current Performance (vs C extension):**
281-
- ✅ Simple encoding: **0.84x** (within variance of reference implementations)
282-
- 🔴 Complex encoding: **0.21x** (5x slower - nested structure overhead)
283-
- 🔴 Decoding: **0.45-0.55x** (2-2.2x slower)
281+
- Simple encoding: **0.84x** (16% slower than C)
282+
- Complex encoding: **0.21x** (5x slower than C)
283+
- Simple decoding: **0.42x** (2.4x slower than C)
284+
- Complex decoding: **0.29x** (3.4x slower than C)
284285

285-
**Key Findings:**
286-
- Encoding performance is reasonable for simple documents
287-
- Main bottleneck: Python/Rust FFI overhead for nested structures
288-
- Reference Copilot POC achieved 2.89x average speedup primarily through **decoding optimizations** (4-5x faster decode), not encoding
286+
**Implementation Status:**
287+
- ✅ Hybrid encoding strategy (fast path for PyDict, `items()` for other mappings)
288+
- ✅ Direct buffer writing with `doc.to_writer()`
289+
- ✅ Efficient `_id` field ordering at top level
290+
- ✅ Direct byte reading for decoding (single-pass bytes → Python dict)
291+
- ✅ 100% test pass rate (88/88 tests)
289292

290-
**Recommendation:** C extension remains the default. Rust extension is available for experimentation and demonstrates feasibility for future development.
293+
**Performance Analysis:**
294+
295+
The Rust extension is currently slower than the C extension for both encoding and decoding. The main bottleneck is **Python FFI overhead** - creating Python objects from Rust incurs significant performance cost.
296+
297+
**Recommendation:** C extension remains the default and recommended choice. The Rust extension demonstrates feasibility and correctness but is not yet performance-competitive for production use.
298+
299+
### Path to Performance Parity
300+
301+
Analysis of the C extension reveals several optimization opportunities to achieve near-parity performance:
302+
303+
#### Priority 1: Type Caching (HIGH IMPACT)
304+
305+
**Problem:** The Rust implementation calls `py.import()` on every BSON type conversion:
306+
```rust
307+
// Called millions of times during decoding!
308+
let int64_module = py.import("bson.int64")?;
309+
let int64_class = int64_module.getattr("Int64")?;
310+
```
311+
312+
**Solution:** Cache Python type objects in module state (like C extension does):
313+
```rust
314+
struct TypeCache {
315+
binary_class: OnceCell<PyObject>,
316+
int64_class: OnceCell<PyObject>,
317+
objectid_class: OnceCell<PyObject>,
318+
// ... etc
319+
}
320+
```
321+
322+
**Expected Impact:** 2-3x faster decoding, 1.5-2x faster encoding
323+
**Effort:** 4-6 hours
324+
325+
#### Priority 2: Fast Paths for Common Types (MEDIUM IMPACT)
326+
327+
**Problem:** Every type conversion has overhead even with caching
328+
329+
**Solution:** Add fast paths for common types:
330+
- Int32/Int64: Use `PyLong_FromLong()` directly when possible
331+
- String: Use `PyUnicode_FromStringAndSize()` directly
332+
- Boolean: Use `Py_True`/`Py_False` singletons
333+
- Null: Use `py.None()` singleton
334+
335+
**Expected Impact:** 1.3-1.5x faster for simple documents
336+
**Effort:** 2-3 hours
337+
338+
#### Priority 3: Reduce Allocations (MEDIUM IMPACT)
339+
340+
**Problem:** Creating intermediate `bson::Document` structures adds overhead
341+
342+
**Solution:** For simple documents, read bytes → Python directly without intermediate Rust structs
343+
344+
**Expected Impact:** 1.2-1.4x faster for simple documents
345+
**Effort:** 6-8 hours (complex refactor)
346+
347+
#### Priority 4: Profile and Optimize Hotspots (LOW-MEDIUM IMPACT)
348+
349+
**Problem:** Unknown bottlenecks may exist
350+
351+
**Solution:** Use `cargo flamegraph` or `py-spy` to profile and identify remaining hotspots
352+
353+
**Expected Impact:** 1.1-1.3x faster overall
354+
**Effort:** 3-4 hours
355+
356+
#### Projected Performance After Optimizations
357+
358+
| Optimization | Simple Encode | Complex Encode | Simple Decode | Complex Decode |
359+
|--------------|---------------|----------------|---------------|----------------|
360+
| **Current** | 0.84x | 0.21x | 0.42x | 0.29x |
361+
| + Type Caching | 1.2x | 0.4x | 1.0x | 0.7x |
362+
| + Fast Paths | 1.5x | 0.5x | 1.3x | 0.9x |
363+
| + Reduce Allocs | 1.8x | 0.6x | 1.5x | 1.0x |
364+
| + Profiling | **2.0x** | **0.7x** | **1.7x** | **1.1x** |
365+
366+
**Note:** Complex encoding will likely remain slower due to Python FFI overhead for nested structures.
367+
368+
**Total Estimated Effort:** 15-21 hours to reach near-parity performance
369+
370+
**Recommended Implementation Order:**
371+
1. Type Caching (Priority 1) - Biggest impact
372+
2. Fast Paths (Priority 2) - Quick wins
373+
3. Profile (Priority 4) - Find remaining bottlenecks
374+
4. Reduce Allocations (Priority 3) - Only if needed after profiling
291375

292376
**Run benchmarks:**
293377
```bash

0 commit comments

Comments
 (0)