use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicU64, Ordering};
use excelreader::workbook::{parse_sheet, ExcelMapper, Workbook};
static BYTES_ALLOCATED: AtomicU64 = AtomicU64::new(0);
struct CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
BYTES_ALLOCATED.fetch_add(layout.size() as u64, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if new_size > layout.size() {
BYTES_ALLOCATED.fetch_add((new_size - layout.size()) as u64, Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;
#[derive(Default, ExcelMapper)]
struct Row {
#[excel(name = "Coluna1")]
coluna1: String,
#[excel(name = "Coluna3")]
coluna3: i64,
}
#[derive(Default, ExcelMapper)]
struct LargeRow {
#[excel(name = "Region")]
region: String,
#[excel(name = "Country")]
country: String,
#[excel(name = "Order Date")]
order_date: excelreader::Date,
#[excel(name = "Order ID")]
order_id: i64,
#[excel(name = "Units Sold")]
units_sold: i64,
#[excel(name = "Total Profit")]
total_profit: f64,
}
fn fixture_path() -> String {
concat!(env!("CARGO_MANIFEST_DIR"), "/../../RealExcel.xlsb").to_string()
}
fn large_fixture_path() -> String {
concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../tests/ExcelReader.Benchmarks/Data/65K_Records_Data.xlsb"
)
.to_string()
}
fn measure<S, R, O>(name: &str, iterations: usize, mut setup: S, mut routine: R)
where
S: FnMut() -> O,
R: FnMut(O),
{
let mut samples = Vec::with_capacity(iterations);
for _ in 0..iterations {
let input = setup();
let before = BYTES_ALLOCATED.load(Ordering::Relaxed);
routine(input);
let after = BYTES_ALLOCATED.load(Ordering::Relaxed);
samples.push(after.saturating_sub(before));
}
samples.sort_unstable();
let min = samples[0];
let max = samples[samples.len() - 1];
let median = samples[samples.len() / 2];
println!("{name:<28} min={min:>10} bytes median={median:>10} bytes max={max:>10} bytes (n={iterations})");
}
fn main() {
let small_path = fixture_path();
let large_path = large_fixture_path();
const ITERATIONS: usize = 200;
const ITERATIONS_LARGE: usize = 30;
println!("Bytes allocated per call, Rust binding (System allocator, this process only — excludes the NativeAOT side of the FFI boundary):\n");
measure(
"open",
ITERATIONS,
|| (),
|()| {
let workbook = Workbook::open(&small_path).expect("open must succeed");
std::hint::black_box(workbook);
},
);
measure(
"parse_sheet",
ITERATIONS,
|| Workbook::open(&small_path).expect("open must succeed"),
|mut workbook| {
let table = parse_sheet::<Row>(&mut workbook, 1).expect("parse_sheet must succeed");
std::hint::black_box(table);
},
);
measure(
"infer_schema",
ITERATIONS,
|| Workbook::open(&small_path).expect("open must succeed"),
|workbook| {
let schema = workbook.infer_schema(1, 100).expect("infer_schema must succeed");
std::hint::black_box(schema);
},
);
measure(
"open_large",
ITERATIONS_LARGE,
|| (),
|()| {
let workbook = Workbook::open(&large_path).expect("open must succeed");
std::hint::black_box(workbook);
},
);
measure(
"parse_sheet_large",
ITERATIONS_LARGE,
|| Workbook::open(&large_path).expect("open must succeed"),
|mut workbook| {
let table = parse_sheet::<LargeRow>(&mut workbook, 1).expect("parse_sheet must succeed");
std::hint::black_box(table);
},
);
measure(
"infer_schema_large",
ITERATIONS_LARGE,
|| Workbook::open(&large_path).expect("open must succeed"),
|workbook| {
let schema = workbook.infer_schema(1, 1000).expect("infer_schema must succeed");
std::hint::black_box(schema);
},
);
}