use std::alloc::{GlobalAlloc, Layout, System};
use std::hint::black_box;
use std::sync::atomic::{AtomicUsize, Ordering};
use truecalc_workbook::{
Address, Cell, CellInput, EngineFlavor, RecalcContext, Value, Workbook, Worksheet,
};
static ALLOCATIONS: AtomicUsize = AtomicUsize::new(0);
struct CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
fn allocations_during<T>(body: impl FnOnce() -> T) -> usize {
let before = ALLOCATIONS.load(Ordering::Relaxed);
black_box(body());
ALLOCATIONS.load(Ordering::Relaxed) - before
}
fn column_of_literals(rows: u32) -> Worksheet {
let mut sheet = Worksheet::new("Sheet1");
for row in 1..=rows {
sheet.cells_mut().insert(
Address::new(row, 1).unwrap().to_a1(),
Cell::literal(Value::Number(f64::from(row))).unwrap(),
);
}
sheet
}
fn scan_of(rows: u32) -> Workbook {
let mut wb = Workbook::new(EngineFlavor::Sheets);
wb.add_sheet(column_of_literals(rows)).unwrap();
wb.set(
"Sheet1",
Address::new(1, 2).unwrap(),
CellInput::Formula(format!("=SUM(A$1:A{rows})")),
)
.unwrap();
wb
}
fn allocations_to_recalc(rows: u32) -> usize {
let ctx = RecalcContext::new(0, "UTC", 0).unwrap();
let mut wb = scan_of(rows);
allocations_during(|| wb.recalc(&ctx))
}
const LOOKUPS: u32 = 10_000;
const MAX_ALLOCATIONS_PER_ELEMENT_SCANNED: f64 = 3.0;
#[test]
fn grid_lookups_do_not_allocate_an_a1_key() {
let sheet = column_of_literals(LOOKUPS);
let addresses: Vec<Address> = (1..=LOOKUPS).map(|r| Address::new(r, 1).unwrap()).collect();
for addr in &addresses {
black_box(sheet.get(*addr));
}
let hits = allocations_during(|| {
for addr in &addresses {
black_box(sheet.get(*addr));
black_box(sheet.contains(*addr));
}
});
assert_eq!(
hits, 0,
"{LOOKUPS} grid lookups allocated {hits} times; a lookup must key the \
cell map by a borrowed &str, never by an owned A1 String"
);
black_box(allocations_to_recalc(64));
let small = allocations_to_recalc(LOOKUPS / 10);
let large = allocations_to_recalc(LOOKUPS / 5);
let extra_elements = f64::from(LOOKUPS / 5 - LOOKUPS / 10);
let per_element = (large - small) as f64 / extra_elements;
assert!(
per_element <= MAX_ALLOCATIONS_PER_ELEMENT_SCANNED,
"scanning one more range element cost {per_element:.2} allocations \
(budget {MAX_ALLOCATIONS_PER_ELEMENT_SCANNED:.2}); \
{small} allocations for {} elements, {large} for {}",
LOOKUPS / 10,
LOOKUPS / 5
);
}