#![cfg(feature = "emissions")]
use std::{
alloc::{GlobalAlloc, Layout, System},
cell::Cell,
};
use asry::{Lang, emissions::EmissionsAligner};
struct CountingAllocator;
thread_local! {
static COUNTING: Cell<bool> = const { Cell::new(false) };
static BYTES: Cell<usize> = const { Cell::new(0) };
}
fn count(bytes: usize) {
let _ = COUNTING.try_with(|counting| {
if counting.get() {
let _ = BYTES.try_with(|total| total.set(total.get() + bytes));
}
});
}
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
count(layout.size());
unsafe { System.alloc(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
count(layout.size());
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
count(new_size);
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
const TOKENIZER_JSON: &str = r#"{
"version": "1.0",
"truncation": null,
"padding": null,
"added_tokens": [],
"normalizer": null,
"pre_tokenizer": {"type": "Split", "pattern": {"Regex": ""}, "behavior": "Isolated", "invert": false},
"post_processor": null,
"decoder": null,
"model": {
"type": "WordLevel",
"vocab": {
"<pad>": 0, "<s>": 1, "</s>": 2, "<unk>": 3, "|": 4,
"E": 5, "T": 6, "A": 7, "O": 8, "N": 9, "I": 10, "H": 11, "S": 12,
"R": 13, "D": 14, "L": 15, "U": 16, "M": 17, "W": 18, "C": 19, "F": 20,
"G": 21, "Y": 22, "P": 23, "B": 24, "V": 25, "K": 26, "'": 27, "X": 28,
"J": 29, "Q": 30, "Z": 31
},
"unk_token": "<unk>"
}
}"#;
fn bytes_to_detect(aligner: &EmissionsAligner, n: usize) -> usize {
let text = "4".repeat(n);
BYTES.with(|total| total.set(0));
COUNTING.with(|counting| counting.set(true));
let detection = aligner.detect_oov(&text);
COUNTING.with(|counting| counting.set(false));
let detection = detection.expect("detect_oov");
assert_eq!(detection.events().len(), n, "one event per digit");
drop(detection);
BYTES.with(Cell::get)
}
#[test]
fn detection_memory_grows_linearly_with_the_text() {
let aligner = EmissionsAligner::builder(Lang::En, TOKENIZER_JSON.as_bytes())
.build()
.expect("build");
bytes_to_detect(&aligner, 64);
let sizes = [2_000_usize, 4_000, 8_000, 16_000];
let bytes: Vec<usize> = sizes
.iter()
.map(|&n| bytes_to_detect(&aligner, n))
.collect();
for (pair, sizes) in bytes.windows(2).zip(sizes.windows(2)) {
assert!(
pair[1] * 10 <= pair[0] * 25,
"doubling the text from {} to {} characters took {} to {} bytes: more than \
linear",
sizes[0],
sizes[1],
pair[0],
pair[1],
);
}
let (&last_bytes, &last_n) = (bytes.last().expect("sizes"), sizes.last().expect("sizes"));
assert!(
last_bytes <= last_n * 512,
"{last_n} characters took {last_bytes} bytes: more than 512 per character"
);
}