Skip to content

Commit ee2735d

Browse files
committed
Optimize DAG-CBOR string decode with ASCII fast path
1 parent 4c7905a commit ee2735d

1 file changed

Lines changed: 40 additions & 3 deletions

File tree

src/lib.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,42 @@ fn collect_and_sort_map_entries<'py>(
168168
Ok(entries)
169169
}
170170

171+
// `PyUnicode_DecodeUTF8` runs a state machine even on pure-ASCII input. Skip
172+
// it by allocating a compact-ASCII `PyUnicode` and memcpying into its inline
173+
// buffer; non-ASCII falls through to the standard decoder.
174+
#[cfg(CPython)]
175+
#[inline]
176+
fn pystring_from_bytes_fast<'py>(
177+
py: Python<'py>,
178+
bytes: &[u8],
179+
) -> PyResult<Bound<'py, PyString>> {
180+
if !bytes.is_ascii() {
181+
return PyString::from_bytes(py, bytes);
182+
}
183+
184+
unsafe {
185+
let obj = ffi::PyUnicode_New(bytes.len() as ffi::Py_ssize_t, 127);
186+
if obj.is_null() {
187+
return Err(PyErr::fetch(py));
188+
}
189+
190+
let data = obj.cast::<ffi::PyASCIIObject>().offset(1).cast::<u8>();
191+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), data, bytes.len());
192+
*data.add(bytes.len()) = 0;
193+
194+
Ok(Bound::from_owned_ptr(py, obj).cast_into_unchecked::<PyString>())
195+
}
196+
}
197+
198+
#[cfg(not(CPython))]
199+
#[inline]
200+
fn pystring_from_bytes_fast<'py>(
201+
py: Python<'py>,
202+
bytes: &[u8],
203+
) -> PyResult<Bound<'py, PyString>> {
204+
PyString::from_bytes(py, bytes)
205+
}
206+
171207
fn get_bytes_from_py_any<'py>(obj: &'py Bound<'py, PyAny>) -> PyResult<&'py [u8]> {
172208
if let Ok(b) = obj.cast::<PyBytes>() {
173209
Ok(b.as_bytes())
@@ -222,8 +258,9 @@ where
222258
.into_pyobject(py)?
223259
.into(),
224260
major::STRING => {
225-
// The UTF-8 validation is done when it's converted into a Python string
226-
PyString::from_bytes(
261+
// ASCII fast path inside the helper; non-ASCII falls through to
262+
// `PyUnicode_DecodeUTF8`, which is where the spec validation lives.
263+
pystring_from_bytes_fast(
227264
py,
228265
<types::UncheckedStr<&[u8]>>::decode(r)
229266
.map_err(|_| anyhow!("Cannot decode as bytes"))?
@@ -277,7 +314,7 @@ where
277314
}
278315
}
279316

280-
let key_py = PyString::from_bytes(py, key)?;
317+
let key_py = pystring_from_bytes_fast(py, key)?;
281318
prev_key = Some(key);
282319

283320
let value_py = decode_dag_cbor_to_pyobject(py, r, depth + 1)?;

0 commit comments

Comments
 (0)