Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,204 @@
|
||||
// 文件创建和清理位于计时之外;同一文件先预热,不把页缓存当作编译器收益。
|
||||
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<bool, Fault> {
|
||||
values.first().map_or(Ok(true), |value| {
|
||||
GENERATED_OPEN(&value.path).map_err(resource_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn reference_open(values: &[ResourceCase]) -> Result<bool, Fault> {
|
||||
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<Vec<u8>, 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<Vec<u8>, 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<i64, Fault> {
|
||||
values.first().map_or(Ok(0), |value| {
|
||||
GENERATED_STREAM(&value.path, value.limit).map_err(resource_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn reference_stream(values: &[ResourceCase]) -> Result<i64, Fault> {
|
||||
values.first().map_or(Ok(0), |value| {
|
||||
reference_consume(&value.path, value.limit, false).map_err(resource_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn generated_partial(values: &[ResourceCase]) -> Result<i64, Fault> {
|
||||
values.first().map_or(Ok(0), |value| {
|
||||
GENERATED_PARTIAL(&value.path, value.limit).map_err(resource_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn reference_partial(values: &[ResourceCase]) -> Result<i64, Fault> {
|
||||
values.first().map_or(Ok(0), |value| {
|
||||
reference_consume(&value.path, value.limit, true).map_err(resource_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn generated_closed(values: &[ResourceCase]) -> Result<bool, Fault> {
|
||||
Ok(values
|
||||
.first()
|
||||
.is_none_or(|value| GENERATED_CLOSED(&value.path).is_err()))
|
||||
}
|
||||
|
||||
fn reference_closed(values: &[ResourceCase]) -> Result<bool, Fault> {
|
||||
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::<Vec<_>>();
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// 独立 Rust 参照保留文件的跨线程别名及同步流的共享游标,不调用 Dever I/O。
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fs,
|
||||
io::Read,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ReferenceFile(Arc<Mutex<Option<fs::File>>>);
|
||||
|
||||
impl ReferenceFile {
|
||||
fn open(path: &str) -> Result<Self, String> {
|
||||
fs::File::open(path)
|
||||
.map(|file| Self(Arc::new(Mutex::new(Some(file)))))
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
fn read(&self, limit: i64) -> Result<Option<Vec<u8>>, String> {
|
||||
let limit = usize::try_from(limit)
|
||||
.ok()
|
||||
.filter(|limit| *limit > 0)
|
||||
.ok_or("read limit must be positive")?;
|
||||
let mut guard = self.0.lock().map_err(|_| "file lock poisoned")?;
|
||||
let file = guard.as_mut().ok_or("file is closed")?;
|
||||
let mut bytes = Vec::new();
|
||||
bytes
|
||||
.try_reserve_exact(limit)
|
||||
.map_err(|error| error.to_string())?;
|
||||
bytes.resize(limit, 0);
|
||||
let count = file.read(&mut bytes).map_err(|error| error.to_string())?;
|
||||
bytes.truncate(count);
|
||||
Ok((count != 0).then_some(bytes))
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), String> {
|
||||
drop(
|
||||
self.0
|
||||
.lock()
|
||||
.map_err(|_| "file lock poisoned")?
|
||||
.take()
|
||||
.ok_or("file is closed")?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ReferenceStream(Rc<RefCell<Option<(ReferenceFile, i64)>>>);
|
||||
|
||||
impl ReferenceStream {
|
||||
fn new(file: &ReferenceFile, limit: i64) -> Result<Self, String> {
|
||||
usize::try_from(limit)
|
||||
.ok()
|
||||
.filter(|limit| *limit > 0)
|
||||
.ok_or("read limit must be positive")?;
|
||||
if file.0.lock().map_err(|_| "file lock poisoned")?.is_none() {
|
||||
return Err("file is closed".into());
|
||||
}
|
||||
Ok(Self(Rc::new(RefCell::new(Some((file.clone(), limit))))))
|
||||
}
|
||||
|
||||
fn next(&self) -> Option<Result<Vec<u8>, String>> {
|
||||
let mut source = self.0.borrow_mut();
|
||||
let (file, limit) = source.as_ref()?;
|
||||
match file.read(*limit) {
|
||||
Ok(Some(bytes)) => Some(Ok(bytes)),
|
||||
Ok(None) => {
|
||||
*source = None;
|
||||
None
|
||||
}
|
||||
Err(error) => {
|
||||
*source = None;
|
||||
Some(Err(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
*self.0.borrow_mut() = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn reference_count(bytes: &[u8], total: i64) -> Result<i64, String> {
|
||||
bytes.iter().try_fold(total, |sum, byte| {
|
||||
sum.checked_add(i64::from(*byte))
|
||||
.ok_or_else(|| "integer overflow".into())
|
||||
})
|
||||
}
|
||||
|
||||
fn reference_consume(path: &str, limit: i64, partial: bool) -> Result<i64, String> {
|
||||
let file = ReferenceFile::open(path)?;
|
||||
let stream = ReferenceStream::new(&file, limit)?;
|
||||
let alias = partial.then(|| stream.clone());
|
||||
let mut total = 0;
|
||||
if partial {
|
||||
if let Some(bytes) = stream.next() {
|
||||
total = reference_count(&bytes?, total)?;
|
||||
}
|
||||
}
|
||||
while let Some(bytes) = alias.as_ref().unwrap_or(&stream).next() {
|
||||
total = reference_count(&bytes?, total)?;
|
||||
}
|
||||
if partial {
|
||||
stream.close();
|
||||
alias.as_ref().unwrap().close();
|
||||
}
|
||||
file.close()?;
|
||||
Ok(total)
|
||||
}
|
||||
Reference in new issue
Block a user