mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-13 22:32:19 +00:00
perf(cos): keep a running total of the scan reader's cache size
ObjectReader::try_get measured every parsed object in its cache (up to 128) after each read to check the byte budget. Keep a running total instead and measure the whole cache only when something other than one freshly parsed object changed it. Eviction timing is unchanged, so output is identical. Fixes #4867 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f94668f1c8
commit
fe03703a3b
1 file changed
+40
-3
@@ -445,6 +445,10 @@ pub struct Document {
|
||||
pub(crate) struct ObjectReader {
|
||||
pub(crate) document: Document,
|
||||
decoded_count: usize,
|
||||
/// Objects in the parsed-object cache and the bytes of their own, as last measured. Kept
|
||||
/// as a running total so a read does not measure the whole cache again.
|
||||
counted_objects: usize,
|
||||
counted_bytes: usize,
|
||||
}
|
||||
|
||||
impl ObjectReader {
|
||||
@@ -488,8 +492,25 @@ impl ObjectReader {
|
||||
// have cached large objects.
|
||||
if let Ok(mut cache) = self.document.cache.lock() {
|
||||
let data = &self.document.data;
|
||||
if cache.len() >= Self::OBJECTS || cache.values().fold(0usize, |n, o| n.saturating_add(owned_bytes(o, data, 0))) > Self::STREAM_BYTES {
|
||||
// The common read parsed exactly this object and nothing else (a hit changes
|
||||
// nothing): add its size to the running total. Anything else, such as a failed
|
||||
// load, a /Length that cached further objects or a cache changed from outside,
|
||||
// measures the whole cache again.
|
||||
let grew_by_this_object = cache.len() == self.counted_objects.saturating_add(1)
|
||||
&& matches!((&object, cache.get(&reference.num)), (Ok(read), Some(cached)) if Arc::ptr_eq(read, cached));
|
||||
if object.is_ok() && cache.len() == self.counted_objects {
|
||||
// Unchanged: a hit, or an object held by the overlay.
|
||||
} else if let (true, Ok(read)) = (grew_by_this_object, &object) {
|
||||
self.counted_bytes = self.counted_bytes.saturating_add(owned_bytes(read, data, 0));
|
||||
self.counted_objects = cache.len();
|
||||
} else {
|
||||
self.counted_bytes = cache.values().fold(0usize, |n, o| n.saturating_add(owned_bytes(o, data, 0)));
|
||||
self.counted_objects = cache.len();
|
||||
}
|
||||
if cache.len() >= Self::OBJECTS || self.counted_bytes > Self::STREAM_BYTES {
|
||||
cache.clear();
|
||||
self.counted_objects = 0;
|
||||
self.counted_bytes = 0;
|
||||
}
|
||||
}
|
||||
object
|
||||
@@ -1176,7 +1197,7 @@ impl Document {
|
||||
let mut document = self.clone();
|
||||
document.cache = Arc::default();
|
||||
document.objstms = Arc::default();
|
||||
ObjectReader { document, decoded_count: 0 }
|
||||
ObjectReader { document, decoded_count: 0, counted_objects: 0, counted_bytes: 0 }
|
||||
}
|
||||
|
||||
fn load(&self, num: u32, depth: u32) -> Result<Object, CosError> {
|
||||
@@ -3004,11 +3025,27 @@ endobj
|
||||
// Data of its own (decrypted streams, strings) counts: past the budget the cache is
|
||||
// cleared after the next read, including a failed one.
|
||||
let owned = Object::Stream(crate::Stream::from_raw(Dict::new(), vec![b'y'; ObjectReader::STREAM_BYTES + 1]));
|
||||
reader.document.cache.lock().unwrap().insert(3, Arc::new(owned));
|
||||
reader.document.cache.lock().unwrap().insert(5, Arc::new(owned));
|
||||
assert_eq!(*reader.get(ObjRef::new(99, 0)), Object::Null);
|
||||
assert!(reader.document.cache.lock().unwrap().is_empty(), "owned data over budget is evicted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temporary_reader_running_total_matches_the_cache() {
|
||||
let mut bodies = vec!["<< /Type /Catalog /Pages 2 0 R >>".to_owned(), "<< /Type /Pages /Kids [] /Count 0 >>".to_owned()];
|
||||
bodies.extend((0..300).map(|i| format!("<< /Value {i} /Name ({}) >>", "s".repeat(i % 40))));
|
||||
let refs: Vec<_> = bodies.iter().map(String::as_str).collect();
|
||||
let doc = Document::open(Arc::new(build(&refs, "/Root 1 0 R"))).unwrap();
|
||||
let mut reader = doc.object_reader();
|
||||
for num in (1..=302).chain([5, 301, 99_999, 7]) {
|
||||
let _ = reader.try_get(ObjRef::new(num, 0));
|
||||
let cache = reader.document.cache.lock().unwrap();
|
||||
let measured = cache.values().fold(0usize, |n, o| n.saturating_add(owned_bytes(o, &reader.document.data, 0)));
|
||||
assert_eq!((reader.counted_objects, reader.counted_bytes), (cache.len(), measured), "after object {num}");
|
||||
assert!(cache.len() < ObjectReader::OBJECTS);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temporary_reader_and_full_save_do_not_fill_snapshot_caches() {
|
||||
let mut source = Document::new_empty();
|
||||
|
||||
Reference in new issue
Block a user