// 文件创建和清理位于计时之外;同一文件先预热,不把页缓存当作编译器收益。 struct ResourceCase { path: String, limit: i64, } struct ResourceDirectory(std::path::PathBuf); impl Drop for ResourceDirectory { fn drop(&mut self) { fs::remove_dir_all(&self.0).unwrap(); } } fn resource_fault(_: impl std::fmt::Display) -> Fault { &FaultInfo { source: "resource-reference", line: 1, column: 1, message: "resource failure", } } fn generated_open(values: &[ResourceCase]) -> Result { values.first().map_or(Ok(true), |value| { GENERATED_OPEN(&value.path).map_err(resource_fault) }) } fn reference_open(values: &[ResourceCase]) -> Result { let Some(value) = values.first() else { return Ok(true); }; let file = ReferenceFile::open(&value.path).map_err(resource_fault)?; let alias = file.clone(); alias.close().map_err(resource_fault)?; Ok(true) } fn generated_read(values: &[ResourceCase]) -> Result, Fault> { values.first().map_or(Ok(Vec::new()), |value| { GENERATED_READ(&value.path, value.limit).map_err(resource_fault) }) } fn reference_read(values: &[ResourceCase]) -> Result, Fault> { let Some(value) = values.first() else { return Ok(Vec::new()); }; let file = ReferenceFile::open(&value.path).map_err(resource_fault)?; let bytes = file .read(value.limit) .map_err(resource_fault)? .unwrap_or_default(); file.close().map_err(resource_fault)?; Ok(bytes) } fn generated_stream(values: &[ResourceCase]) -> Result { values.first().map_or(Ok(0), |value| { GENERATED_STREAM(&value.path, value.limit).map_err(resource_fault) }) } fn reference_stream(values: &[ResourceCase]) -> Result { values.first().map_or(Ok(0), |value| { reference_consume(&value.path, value.limit, false).map_err(resource_fault) }) } fn generated_partial(values: &[ResourceCase]) -> Result { values.first().map_or(Ok(0), |value| { GENERATED_PARTIAL(&value.path, value.limit).map_err(resource_fault) }) } fn reference_partial(values: &[ResourceCase]) -> Result { values.first().map_or(Ok(0), |value| { reference_consume(&value.path, value.limit, true).map_err(resource_fault) }) } fn generated_closed(values: &[ResourceCase]) -> Result { Ok(values .first() .is_none_or(|value| GENERATED_CLOSED(&value.path).is_err())) } fn reference_closed(values: &[ResourceCase]) -> Result { let Some(value) = values.first() else { return Ok(true); }; let file = ReferenceFile::open(&value.path).map_err(resource_fault)?; let stream = ReferenceStream::new(&file, 64).map_err(resource_fault)?; file.close().map_err(resource_fault)?; Ok(stream.next().unwrap().is_err()) } fn main() { let path = std::env::temp_dir().join(format!("dever-resource-performance-{}", std::process::id())); fs::create_dir(&path).unwrap(); let directory = ResourceDirectory(path); let path = directory.0.join("input"); let bytes = (0..65537) .map(|index| (index % 251) as u8) .collect::>(); fs::write(&path, &bytes).unwrap(); let path = path.to_str().unwrap().to_owned(); assert_eq!(GENERATED_READ(&path, 1_000_000).unwrap(), bytes); for limit in [0, -1, i64::MAX] { assert!(GENERATED_READ(&path, limit).is_err()); assert!(ReferenceFile::open(&path).unwrap().read(limit).is_err()); } let expected: i64 = bytes.iter().map(|byte| i64::from(*byte)).sum(); for limit in [64, 4096] { assert_eq!(GENERATED_STREAM(&path, limit).unwrap(), expected); assert_eq!(GENERATED_PARTIAL(&path, limit).unwrap(), expected); } let closed = GENERATED_CLOSED(&path).unwrap_err(); assert!( closed.to_string().contains("ReadEvent.Failed") && format!("{closed:?}").contains("file is closed"), "{closed}" ); let file = ReferenceFile::open(&path).unwrap(); let alias = file.clone(); assert_eq!(file.read(2).unwrap().unwrap(), bytes[..2]); assert_eq!(alias.read(2).unwrap().unwrap(), bytes[2..4]); let stream = ReferenceStream::new(&file, 2).unwrap(); alias.close().unwrap(); assert!(stream.next().unwrap().is_err()); assert!(stream.next().is_none()); println!("case,sample,implementation,elapsed_ns,checksum"); let values = [ResourceCase { path: path.clone(), limit: 64, }]; compare( "file_open_close", &values, 2000, generated_open, reference_open, |value| u64::from(*value), ); compare( "file_read_64", &values, 2000, generated_read, reference_read, |value| value.len() as u64, ); compare( "stream_read_64", &values, 50, generated_stream, reference_stream, |value| *value as u64, ); compare( "stream_resume_64", &values, 50, generated_partial, reference_partial, |value| *value as u64, ); compare( "stream_closed", &values, 2000, generated_closed, reference_closed, |value| u64::from(*value), ); let values = [ResourceCase { path, limit: 4096 }]; compare( "file_read_4096", &values, 1000, generated_read, reference_read, |value| value.len() as u64, ); compare( "stream_read_4096", &values, 200, generated_stream, reference_stream, |value| *value as u64, ); compare( "stream_resume_4096", &values, 200, generated_partial, reference_partial, |value| *value as u64, ); }