use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use dora_node_api::arrow::array::{Array, Float32Array};
use dora_node_api::arrow_utils::ipc_encode::{
encode_ipc_into, encode_ipc_to_vec, encode_uint8_ipc_header, ipc_fast_path_len, uint8_ipc_len,
};
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if new_size > layout.size() {
ALLOCATED.fetch_add(new_size - layout.size(), Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn allocated_during<R>(f: impl FnOnce() -> R) -> (usize, R) {
let before = ALLOCATED.load(Ordering::Relaxed);
let r = f();
let after = ALLOCATED.load(Ordering::Relaxed);
(after.wrapping_sub(before), r)
}
const ELEMENTS: usize = 1_048_576; const PAYLOAD: usize = ELEMENTS * 4;
fn big_array() -> dora_node_api::arrow::array::ArrayData {
Float32Array::from((0..ELEMENTS).map(|i| i as f32).collect::<Vec<_>>()).into_data()
}
const NO_PAYLOAD_INTERMEDIATE: usize = PAYLOAD / 8;
#[test]
fn send_strategies_intermediate_allocation() {
{
let mut sample = vec![0u8; PAYLOAD];
sample.iter_mut().for_each(|b| *b = 0); let (allocated, ()) = allocated_during(|| {
for (i, chunk) in sample.chunks_exact_mut(4).enumerate() {
chunk.copy_from_slice(&(i as f32).to_le_bytes());
}
});
assert!(
allocated < NO_PAYLOAD_INTERMEDIATE,
"construct-in-place allocated {allocated} bytes (>= {NO_PAYLOAD_INTERMEDIATE}); \
expected no payload-sized intermediate"
);
}
{
let total = uint8_ipc_len(PAYLOAD).unwrap();
let mut sample = vec![0u8; total];
sample.iter_mut().for_each(|b| *b = 0); let (allocated, ()) = allocated_during(|| {
let offset = encode_uint8_ipc_header(&mut sample, PAYLOAD).unwrap();
for (i, b) in sample[offset..offset + PAYLOAD].iter_mut().enumerate() {
*b = i as u8;
}
});
assert!(
allocated < NO_PAYLOAD_INTERMEDIATE,
"send_output_raw construct-in-place allocated {allocated} bytes \
(>= {NO_PAYLOAD_INTERMEDIATE}); the header is tiny and the data is \
written in place, so there must be no payload-sized intermediate"
);
}
{
let data = big_array();
let len = ipc_fast_path_len(&data).expect("primitive array is fast-path eligible");
let mut sample = vec![0u8; len];
sample.iter_mut().for_each(|b| *b = 0); let (allocated, result) = allocated_during(|| encode_ipc_into(&data, &mut sample));
result.expect("fast-path encode");
assert!(
allocated < NO_PAYLOAD_INTERMEDIATE,
"IPC fast path allocated {allocated} bytes (>= {NO_PAYLOAD_INTERMEDIATE}); \
it must write the payload straight into the sample with no body Vec"
);
}
{
let data = big_array();
let (allocated, vec) = allocated_during(|| encode_ipc_to_vec(&data).unwrap());
assert!(
vec.len() >= PAYLOAD,
"fallback stream {} should contain the whole payload",
vec.len()
);
assert!(
allocated >= PAYLOAD,
"IPC fallback allocated only {allocated} bytes (< {PAYLOAD}); \
the official writer is expected to stage a payload-sized Vec"
);
}
}