@@ -7,11 +7,29 @@ use rustler::sys::{
77 ErlNifCharEncoding , ErlNifEnv , ERL_NIF_TERM ,
88} ;
99use rustler:: { schedule, Env , MapIterator , NewBinary , Term , TermType } ;
10+ use std:: cell:: RefCell ;
1011use std:: mem:: MaybeUninit ;
1112
1213const BYTES_PER_REDUCTION : usize = 20 ;
1314const REDUCTION_COUNT : usize = 4000 ;
1415
16+ /// Below this output size the work is sub-microsecond, so the
17+ /// enif_consume_timeslice call costs more than the scheduler accounting is
18+ /// worth. The BEAM's fixed per-NIF-call reduction charge already covers it.
19+ const TIMESLICE_MIN_BYTES : usize = 4096 ;
20+
21+ /// Cap on the retained thread-local scratch buffer, so a one-off huge document
22+ /// doesn't pin a large allocation on a scheduler thread indefinitely.
23+ const BUF_RETAIN_CAP : usize = 1 << 20 ;
24+
25+ thread_local ! {
26+ /// Reused across encode calls on each scheduler thread. Avoids a
27+ /// malloc/free per call, which is the dominant per-call cost for small
28+ /// payloads. NIFs run to completion without preemption and the encoder
29+ /// never re-enters this NIF, so the borrow is never nested.
30+ static ENCODE_BUF : RefCell <Vec <u8 >> = RefCell :: new( Vec :: with_capacity( 2048 ) ) ;
31+ }
32+
1533/// Compute a timeslice percentage (1–100) proportional to bytes processed.
1634#[ inline]
1735fn timeslice_percent ( bytes : usize ) -> i32 {
@@ -29,6 +47,30 @@ enum EncodeError {
2947 InvalidUtf8 ,
3048}
3149
50+ #[ inline]
51+ fn error_reason ( e : EncodeError ) -> ERL_NIF_TERM {
52+ match e {
53+ EncodeError :: DepthExceeded => atoms:: nesting_too_deep ( ) . as_c_arg ( ) ,
54+ EncodeError :: UnsupportedType => atoms:: unsupported_type ( ) . as_c_arg ( ) ,
55+ EncodeError :: NonFiniteFloat => atoms:: non_finite_float ( ) . as_c_arg ( ) ,
56+ EncodeError :: InvalidKey => atoms:: invalid_key ( ) . as_c_arg ( ) ,
57+ EncodeError :: MalformedProplist => atoms:: malformed_proplist ( ) . as_c_arg ( ) ,
58+ EncodeError :: InvalidUtf8 => atoms:: invalid_utf8 ( ) . as_c_arg ( ) ,
59+ }
60+ }
61+
62+ /// Hand the finished scratch buffer to a freshly allocated Erlang binary,
63+ /// reporting the work to the scheduler only when it's large enough to matter.
64+ #[ inline]
65+ fn buf_to_binary < ' a > ( env : Env < ' a > , buf : & [ u8 ] ) -> Term < ' a > {
66+ if buf. len ( ) >= TIMESLICE_MIN_BYTES {
67+ schedule:: consume_timeslice ( env, timeslice_percent ( buf. len ( ) ) ) ;
68+ }
69+ let mut binary = NewBinary :: new ( env, buf. len ( ) ) ;
70+ binary. as_mut_slice ( ) . copy_from_slice ( buf) ;
71+ binary. into ( )
72+ }
73+
3274/// Read an atom's name into a stack buffer without heap allocation.
3375#[ inline]
3476unsafe fn atom_to_stack_buf (
@@ -62,55 +104,46 @@ unsafe fn atom_to_stack_buf(
62104
63105#[ rustler:: nif]
64106fn encode < ' a > ( env : Env < ' a > , term : Term < ' a > ) -> Term < ' a > {
65- let mut buf: Vec < u8 > = Vec :: with_capacity ( 2048 ) ;
66107 let env_raw = env. as_c_arg ( ) ;
67- match encode_term ( env, env_raw, term, & mut buf, MAX_DEPTH ) {
68- Ok ( ( ) ) => {
69- schedule:: consume_timeslice ( env, timeslice_percent ( buf. len ( ) ) ) ;
70- let mut binary = NewBinary :: new ( env, buf. len ( ) ) ;
71- binary. as_mut_slice ( ) . copy_from_slice ( & buf) ;
72- let bin_term: Term = binary. into ( ) ;
73- make_tuple2 ( env, atoms:: ok ( ) . as_c_arg ( ) , bin_term. as_c_arg ( ) )
74- }
75- Err ( e) => {
76- let reason = match e {
77- EncodeError :: DepthExceeded => atoms:: nesting_too_deep ( ) . as_c_arg ( ) ,
78- EncodeError :: UnsupportedType => atoms:: unsupported_type ( ) . as_c_arg ( ) ,
79- EncodeError :: NonFiniteFloat => atoms:: non_finite_float ( ) . as_c_arg ( ) ,
80- EncodeError :: InvalidKey => atoms:: invalid_key ( ) . as_c_arg ( ) ,
81- EncodeError :: MalformedProplist => atoms:: malformed_proplist ( ) . as_c_arg ( ) ,
82- EncodeError :: InvalidUtf8 => atoms:: invalid_utf8 ( ) . as_c_arg ( ) ,
83- } ;
84- make_tuple2 ( env, atoms:: error ( ) . as_c_arg ( ) , reason)
108+ ENCODE_BUF . with ( |cell| {
109+ let mut buf = cell. borrow_mut ( ) ;
110+ buf. clear ( ) ;
111+ let result = match encode_term ( env, env_raw, term, & mut buf, MAX_DEPTH ) {
112+ Ok ( ( ) ) => {
113+ let bin_term = buf_to_binary ( env, & buf) ;
114+ make_tuple2 ( env, atoms:: ok ( ) . as_c_arg ( ) , bin_term. as_c_arg ( ) )
115+ }
116+ Err ( e) => make_tuple2 ( env, atoms:: error ( ) . as_c_arg ( ) , error_reason ( e) ) ,
117+ } ;
118+ if buf. capacity ( ) > BUF_RETAIN_CAP {
119+ buf. shrink_to ( BUF_RETAIN_CAP ) ;
85120 }
86- }
121+ result
122+ } )
87123}
88124
89125/// Returns the raw binary on success, raises on error.
90126/// Skips the {:ok, binary} tuple wrapping for maximum throughput.
91127#[ rustler:: nif]
92128fn encode_iodata < ' a > ( env : Env < ' a > , term : Term < ' a > ) -> Term < ' a > {
93- let mut buf: Vec < u8 > = Vec :: with_capacity ( 2048 ) ;
94129 let env_raw = env. as_c_arg ( ) ;
95- match encode_term ( env, env_raw, term, & mut buf, MAX_DEPTH ) {
96- Ok ( ( ) ) => {
97- schedule:: consume_timeslice ( env, timeslice_percent ( buf. len ( ) ) ) ;
98- let mut binary = NewBinary :: new ( env, buf. len ( ) ) ;
99- binary. as_mut_slice ( ) . copy_from_slice ( & buf) ;
100- binary. into ( )
130+ ENCODE_BUF . with ( |cell| {
131+ let mut buf = cell. borrow_mut ( ) ;
132+ buf. clear ( ) ;
133+ let result = match encode_term ( env, env_raw, term, & mut buf, MAX_DEPTH ) {
134+ Ok ( ( ) ) => buf_to_binary ( env, & buf) ,
135+ Err ( e) => unsafe {
136+ Term :: new (
137+ env,
138+ rustler:: sys:: enif_raise_exception ( env_raw, error_reason ( e) ) ,
139+ )
140+ } ,
141+ } ;
142+ if buf. capacity ( ) > BUF_RETAIN_CAP {
143+ buf. shrink_to ( BUF_RETAIN_CAP ) ;
101144 }
102- Err ( e) => unsafe {
103- let reason = match e {
104- EncodeError :: DepthExceeded => atoms:: nesting_too_deep ( ) . as_c_arg ( ) ,
105- EncodeError :: UnsupportedType => atoms:: unsupported_type ( ) . as_c_arg ( ) ,
106- EncodeError :: NonFiniteFloat => atoms:: non_finite_float ( ) . as_c_arg ( ) ,
107- EncodeError :: InvalidKey => atoms:: invalid_key ( ) . as_c_arg ( ) ,
108- EncodeError :: MalformedProplist => atoms:: malformed_proplist ( ) . as_c_arg ( ) ,
109- EncodeError :: InvalidUtf8 => atoms:: invalid_utf8 ( ) . as_c_arg ( ) ,
110- } ;
111- Term :: new ( env, rustler:: sys:: enif_raise_exception ( env_raw, reason) )
112- } ,
113- }
145+ result
146+ } )
114147}
115148
116149#[ inline]
0 commit comments