Skip to content

Commit 05883cf

Browse files
authored
Optimize int encode with direct PyLong digit read (#107)
1 parent d2eaf4d commit 05883cf

1 file changed

Lines changed: 38 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,11 +652,49 @@ where
652652
}
653653
}
654654

655+
// CPython 3.12+ PyLongObject layout: `PyObject_HEAD; uintptr_t lv_tag; digit ob_digit[]`.
656+
// `lv_tag` packs the sign in the low 3 bits (0=positive, 1=zero, 2=negative) and the
657+
// digit count in the upper bits. Default builds use 30-bit digits (uint32_t).
658+
#[cfg(all(CPython, Py_3_12))]
659+
#[inline]
660+
unsafe fn pylong_to_dag_int_fast(obj: *mut ffi::PyObject) -> Option<(u64, bool)> {
661+
const NON_SIZE_BITS: u32 = 3;
662+
const SIGN_MASK: usize = 3;
663+
const SIGN_NEGATIVE: usize = 2;
664+
const PYLONG_DIGIT_BITS: u32 = 30;
665+
666+
let lv_tag_ptr = (obj as *const u8).add(std::mem::size_of::<ffi::PyObject>()) as *const usize;
667+
let lv_tag = *lv_tag_ptr;
668+
let ndigits = lv_tag >> NON_SIZE_BITS;
669+
let neg = (lv_tag & SIGN_MASK) == SIGN_NEGATIVE;
670+
671+
let ob_digit = lv_tag_ptr.add(1) as *const u32;
672+
let abs_val: u64 = match ndigits {
673+
0 => return Some((0, false)),
674+
1 => *ob_digit as u64,
675+
2 => (*ob_digit as u64) | ((*ob_digit.add(1) as u64) << PYLONG_DIGIT_BITS),
676+
_ => return None,
677+
};
678+
Some((abs_val, neg))
679+
}
680+
655681
#[inline]
656682
fn encode_int<W: enc::Write>(obj: &Bound<'_, PyAny>, w: &mut W) -> Result<()>
657683
where
658684
W::Error: Send + Sync,
659685
{
686+
#[cfg(all(CPython, Py_3_12))]
687+
{
688+
if let Some((abs_val, neg)) = unsafe { pylong_to_dag_int_fast(obj.as_ptr()) } {
689+
if neg {
690+
types::Negative(abs_val - 1).encode(w)?;
691+
} else {
692+
abs_val.encode(w)?;
693+
}
694+
return Ok(());
695+
}
696+
}
697+
660698
let i: i128 = obj.extract()?;
661699
if i.is_negative() {
662700
if -(i + 1) > u64::MAX as i128 {

0 commit comments

Comments
 (0)