Skip to content

Commit 4ea29bb

Browse files
authored
Optimize DAG-CBOR string decode with ASCII fast path (#105)
1 parent 4c7905a commit 4ea29bb

1 file changed

Lines changed: 34 additions & 3 deletions

File tree

src/lib.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,36 @@ 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>(py: Python<'py>, bytes: &[u8]) -> PyResult<Bound<'py, PyString>> {
177+
if !bytes.is_ascii() {
178+
return PyString::from_bytes(py, bytes);
179+
}
180+
181+
unsafe {
182+
let obj = ffi::PyUnicode_New(bytes.len() as ffi::Py_ssize_t, 127);
183+
if obj.is_null() {
184+
return Err(PyErr::fetch(py));
185+
}
186+
187+
let data = obj.cast::<ffi::PyASCIIObject>().offset(1).cast::<u8>();
188+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), data, bytes.len());
189+
*data.add(bytes.len()) = 0;
190+
191+
Ok(Bound::from_owned_ptr(py, obj).cast_into_unchecked::<PyString>())
192+
}
193+
}
194+
195+
#[cfg(not(CPython))]
196+
#[inline]
197+
fn pystring_from_bytes_fast<'py>(py: Python<'py>, bytes: &[u8]) -> PyResult<Bound<'py, PyString>> {
198+
PyString::from_bytes(py, bytes)
199+
}
200+
171201
fn get_bytes_from_py_any<'py>(obj: &'py Bound<'py, PyAny>) -> PyResult<&'py [u8]> {
172202
if let Ok(b) = obj.cast::<PyBytes>() {
173203
Ok(b.as_bytes())
@@ -222,8 +252,9 @@ where
222252
.into_pyobject(py)?
223253
.into(),
224254
major::STRING => {
225-
// The UTF-8 validation is done when it's converted into a Python string
226-
PyString::from_bytes(
255+
// ASCII fast path inside the helper; non-ASCII falls through to
256+
// `PyUnicode_DecodeUTF8`, which is where the spec validation lives.
257+
pystring_from_bytes_fast(
227258
py,
228259
<types::UncheckedStr<&[u8]>>::decode(r)
229260
.map_err(|_| anyhow!("Cannot decode as bytes"))?
@@ -277,7 +308,7 @@ where
277308
}
278309
}
279310

280-
let key_py = PyString::from_bytes(py, key)?;
311+
let key_py = pystring_from_bytes_fast(py, key)?;
281312
prev_key = Some(key);
282313

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

0 commit comments

Comments
 (0)