use scirs2_core::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
use scirs2_core::memory_efficient::{AccessMode, MemoryMappedArray, ZeroCopySerializable};
use scirs2_core::ndarray_ext::{Array, Array1, Array2, Array3, IxDyn};
use serde_json::json;
use std::fs::File;
use std::io::Write;
use std::mem;
use std::path::Path;
use std::slice;
use std::time::Instant;
use tempfile::tempdir;
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
struct Complex64 {
real: f64,
imag: f64,
}
impl Complex64 {
fn new(real: f64, imag: f64) -> Self {
Self { real, imag }
}
fn magnitude(&self) -> f64 {
(self.real * self.real + self.imag * self.imag).sqrt()
}
}
impl ZeroCopySerializable for Complex64 {
unsafe fn from_bytes(bytes: &[u8]) -> CoreResult<Self> {
if !Self::validate_bytes(bytes) {
return Err(CoreError::ValidationError(
ErrorContext::new(format!(
"Invalid byte length for Complex64: expected {} got {}",
mem::size_of::<Self>(),
bytes.len()
))
.with_location(ErrorLocation::new(file!(), line!())),
));
}
let ptr = bytes.as_ptr() as *const Self;
Ok(*ptr)
}
unsafe fn as_bytes(&self) -> &[u8] {
let ptr = self as *const Self as *const u8;
slice::from_raw_parts(ptr, mem::size_of::<Self>())
}
fn type_identifier() -> &'static str {
"Complex64"
}
}
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Memory-Mapped Array Zero-Copy Serialization Example");
println!("==================================================\n");
let dir = tempdir()?;
println!("Using temporary directory: {:?}", dir.path());
basic_serialization_example(dir.path())?;
metadata_example(dir.path())?;
multidimensional_example(dir.path())?;
performance_comparison(dir.path())?;
updating_data_example(dir.path())?;
custom_type_example(dir.path())?;
println!("\nAll examples completed successfully!");
Ok(())
}
#[allow(dead_code)]
fn custom_type_example(tempdir: &Path) -> Result<(), Box<dyn std::error::Error>> {
println!("\n6. Custom Type Serialization Example");
println!("-----------------------------------");
let size = 10;
println!("Creating a {}x{} array of complex numbers", size, size);
let data = Array2::<Complex64>::from_shape_fn((size, size), |(i, j)| {
let distance = ((i as f64 - size as f64 / 2.0).powi(2)
+ (j as f64 - size as f64 / 2.0).powi(2))
.sqrt();
let angle =
((j as f64 - size as f64 / 2.0) / (i as f64 - size as f64 / 2.0 + 0.001)).atan();
Complex64::new(distance * angle.cos(), distance * angle.sin())
});
println!("\nSample of the complex array (3x3 corner):");
for i in 0..3 {
let mut row = String::new();
for j in 0..3 {
let c = data[[i, j]];
row.push_str(&format!("({:.2}+{:.2}i) ", c.real, c.imag));
}
println!(" {}", row);
}
let file_path = tempdir.join("complex_array.bin");
let metadata = json!({
"description": "Complex number array with spiral pattern",
"type": "Complex64",
"dimensions": [size, size],
"created": "2023-05-20",
"custom_properties": {
"pattern": "spiral",
"element_format": "real+imaginary"
}
});
let start = Instant::now();
MemoryMappedArray::<Complex64>::save_array(&data, &file_path, Some(metadata.clone()))?;
let save_time = start.elapsed();
println!(
"\nSaved complex array with zero-copy serialization in {:?}",
save_time
);
println!("File size: {} bytes", file_path.metadata()?.len());
let start = Instant::now();
let loaded = MemoryMappedArray::<Complex64>::open_zero_copy(&file_path, AccessMode::ReadOnly)?;
let load_time = start.elapsed();
println!(
"Loaded complex array with zero-copy deserialization in {:?}",
load_time
);
let loaded_array = loaded.readonlyarray::<scirs2_core::ndarray::Ix2>()?;
println!("\nCalculating magnitudes from loaded array (3x3 corner):");
for i in 0..3 {
let mut row = String::new();
for j in 0..3 {
let c = loaded_array[[i, j]];
let mag = c.magnitude();
row.push_str(&format!("{:.2} ", mag));
}
println!(" {}", row);
}
let mut equal = true;
for i in 0..size {
for j in 0..size {
if data[[i, j]] != loaded_array[[i, j]] {
equal = false;
println!("Data mismatch at [{}, {}]", i, j);
break;
}
}
if !equal {
break;
}
}
if equal {
println!("\nVerification successful: All complex values loaded correctly");
}
let loaded_metadata = MemoryMappedArray::<Complex64>::read_metadata(&file_path)?;
println!("\nMetadata from file:");
println!(" Description: {}", loaded_metadata["description"]);
println!(" Type: {}", loaded_metadata["type"]);
println!(
" Pattern: {}",
loaded_metadata["custom_properties"]["pattern"]
);
Ok(())
}
#[allow(dead_code)]
fn basic_serialization_example(tempdir: &Path) -> Result<(), Box<dyn std::error::Error>> {
println!("\n1. Basic Zero-Copy Serialization Example");
println!("-----------------------------------------");
let size = 1_000_000;
let data = Array1::<f64>::linspace(0.0, 999_999.0, size);
println!("Created a 1D array with {} elements", size);
let file_path = tempdir.join("basic_example.bin");
let metadata = json!({
"description": "Basic example array",
"created": "2023-05-20",
"elements": size,
"element_type": "f64"
});
let start = Instant::now();
let mmap = MemoryMappedArray::<f64>::save_array(&data, &file_path, Some(metadata))?;
let save_time = start.elapsed();
println!(
"Saved array with zero-copy serialization in {:?}",
save_time
);
println!("File size: {} bytes", file_path.metadata()?.len());
let start = Instant::now();
let loaded = MemoryMappedArray::<f64>::open_zero_copy(&file_path, AccessMode::ReadOnly)?;
let load_time = start.elapsed();
println!(
"Loaded array with zero-copy deserialization in {:?}",
load_time
);
let loaded_array = loaded.readonlyarray::<scirs2_core::ndarray::Ix1>()?;
println!(
"Loaded array shape: {:?}, elements: {}",
loaded_array.shape(),
loaded_array.len()
);
println!("Verifying values:");
println!(" Element [0]: {}", loaded_array[0]);
println!(" Element [500000]: {}", loaded_array[500000]);
println!(" Element [999999]: {}", loaded_array[999999]);
let loaded_metadata = MemoryMappedArray::<f64>::read_metadata(&file_path)?;
println!("\nMetadata from file:");
println!(" Description: {}", loaded_metadata["description"]);
println!(" Created: {}", loaded_metadata["created"]);
println!(" Elements: {}", loaded_metadata["elements"]);
println!(" Element type: {}", loaded_metadata["element_type"]);
Ok(())
}
#[allow(dead_code)]
fn metadata_example(tempdir: &Path) -> Result<(), Box<dyn std::error::Error>> {
println!("\n2. Working with Metadata Example");
println!("--------------------------------");
let data = Array1::<f32>::linspace(0.0, 99.0, 100);
println!("Created a small array with 100 elements");
let file_path = tempdir.join("metadata_example.bin");
let initial_metadata = json!({
"description": "Scientific dataset example",
"version": "1.0",
"author": "SciRS2 Team",
"created": "2023-05-20T10:30:00Z",
"license": "Apache-2.0",
"properties": {
"samplingrate": 1000,
"units": "meters",
"calibration_factor": 1.05,
"valid_range": [0.0, 100.0]
},
"tags": ["example", "scientific", "numeric"]
});
MemoryMappedArray::<f32>::save_array(&data, &file_path, Some(initial_metadata))?;
println!("Saved array with rich metadata");
let metadata = MemoryMappedArray::<f32>::read_metadata(&file_path)?;
println!("\nMetadata before update:");
println!(" Description: {}", metadata["description"]);
println!(" Version: {}", metadata["version"]);
println!(
" Sampling rate: {}",
metadata["properties"]["samplingrate"]
);
println!("\nUpdating metadata...");
let updated_metadata = json!({
"description": "Scientific dataset example - Updated",
"version": "1.1",
"author": "SciRS2 Team",
"created": "2023-05-20T10:30:00Z",
"updated": "2023-05-20T11:45:00Z",
"license": "Apache-2.0",
"properties": {
"samplingrate": 1000,
"units": "meters",
"calibration_factor": 1.08, "valid_range": [0.0, 100.0],
"processing": "filtered" },
"tags": ["example", "scientific", "numeric", "processed"]
});
MemoryMappedArray::<f32>::update_metadata(&file_path, updated_metadata)?;
let updated = MemoryMappedArray::<f32>::read_metadata(&file_path)?;
println!("\nMetadata after update:");
println!(" Description: {}", updated["description"]);
println!(" Version: {}", updated["version"]);
println!(" Updated: {}", updated["updated"]);
println!(
" Calibration factor: {}",
updated["properties"]["calibration_factor"]
);
println!(" Processing: {}", updated["properties"]["processing"]);
println!(" Tags: {}", updated["tags"]);
let loaded = MemoryMappedArray::<f32>::open_zero_copy(&file_path, AccessMode::ReadOnly)?;
let loaded_array = loaded.readonlyarray::<scirs2_core::ndarray::Ix1>()?;
println!("\nVerifying data integrity after metadata update:");
println!(" Element [0]: {}", loaded_array[0]);
println!(" Element [50]: {}", loaded_array[50]);
println!(" Element [99]: {}", loaded_array[99]);
Ok(())
}
#[allow(dead_code)]
fn multidimensional_example(tempdir: &Path) -> Result<(), Box<dyn std::error::Error>> {
println!("\n3. Multidimensional Array Example");
println!("---------------------------------");
let data = Array3::<i32>::from_shape_fn((5, 5, 5), |(i, j, k)| (i * 25 + j * 5 + k) as i32);
println!("Created a 3D array with shape {:?}", data.shape());
let file_path = tempdir.join("3d_array.bin");
MemoryMappedArray::<i32>::save_array(&data, &file_path, None)?;
println!("Saved 3D array to file");
let loaded = MemoryMappedArray::<i32>::open_zero_copy(&file_path, AccessMode::ReadOnly)?;
println!("Loaded 3D array from file");
let loaded_array = loaded.readonlyarray::<scirs2_core::ndarray::Ix3>()?;
println!(
"Loaded array shape: {:?}, elements: {}",
loaded_array.shape(),
loaded_array.len()
);
println!("\nSlice of the 3D array (z=2):");
for i in 0..5 {
let mut row = String::new();
for j in 0..5 {
row.push_str(&format!("{:3} ", loaded_array[[i, j, 2]]));
}
println!(" {}", row);
}
println!("\nCreating and saving a dynamic-dimension array...");
let dyn_data = Array::from_shape_fn(IxDyn(&[3, 4, 2, 5]), |idx| {
let mut val = 0;
let mut factor = 1;
for i in 0..4 {
val += idx[3 - i] * factor;
factor *= 10;
}
val as f64
});
let dyn_file_path = tempdir.join("dyn_array.bin");
MemoryMappedArray::<f64>::save_array(&dyn_data, &dyn_file_path, None)?;
let loaded_dyn =
MemoryMappedArray::<f64>::open_zero_copy(&dyn_file_path, AccessMode::ReadOnly)?;
let loaded_dyn_array = loaded_dyn.readonlyarray::<scirs2_core::ndarray::IxDyn>()?;
println!(
"Loaded dynamic array shape: {:?}, elements: {}",
loaded_dyn_array.shape(),
loaded_dyn_array.len()
);
println!("Some values from the dynamic array:");
println!(
" Value at [0,0,0,0]: {}",
loaded_dyn_array[IxDyn(&[0, 0, 0, 0])]
);
println!(
" Value at [1,2,1,3]: {}",
loaded_dyn_array[IxDyn(&[1, 2, 1, 3])]
);
println!(
" Value at [2,3,1,4]: {}",
loaded_dyn_array[IxDyn(&[2, 3, 1, 4])]
);
Ok(())
}
#[allow(dead_code)]
fn performance_comparison(tempdir: &Path) -> Result<(), Box<dyn std::error::Error>> {
println!("\n4. Performance Comparison Example");
println!("--------------------------------");
let size = 1000;
let data = Array2::<f64>::from_shape_fn((size, size), |(i, j)| (i * size + j) as f64);
println!("Created a {}x{} array for performance testing", size, size);
let memory_size = data.len() * std::mem::size_of::<f64>();
println!(
"Array size in memory: {:.2} MB",
memory_size as f64 / (1024.0 * 1024.0)
);
let zero_copy_path = tempdir.join("zero_copy_perf.bin");
let start = Instant::now();
MemoryMappedArray::<f64>::save_array(&data, &zero_copy_path, None)?;
let zero_copy_save_time = start.elapsed();
let traditional_path = tempdir.join("traditional_perf.bin");
let start = Instant::now();
let serialized = oxicode::serde::encode_to_vec(&data, oxicode::config::standard())?;
let mut file = File::create(&traditional_path)?;
file.write_all(&serialized)?;
let traditional_save_time = start.elapsed();
let start = Instant::now();
let loaded_zero_copy =
MemoryMappedArray::<f64>::open_zero_copy(&zero_copy_path, AccessMode::ReadOnly)?;
let zero_copy_load_time = start.elapsed();
let start = Instant::now();
let mut file = File::open(&traditional_path)?;
let mut buffer = Vec::new();
std::io::Read::read_to_end(&mut file, &mut buffer)?;
let (loaded_traditional, _len): (Array2<f64>, usize) =
oxicode::serde::decode_owned_from_slice(&buffer, oxicode::config::standard())?;
let traditional_load_time = start.elapsed();
let loaded = MemoryMappedArray::<f64>::open_zero_copy(&zero_copy_path, AccessMode::ReadOnly)?;
let start = Instant::now();
let array = loaded.readonlyarray::<scirs2_core::ndarray::Ix2>()?;
let mut _sum = 0.0;
for i in 0..10 {
for j in 0..10 {
_sum += array[[i, j]];
}
}
let zero_copy_access_time = start.elapsed();
let (loaded_traditional, _len): (Array2<f64>, usize) =
oxicode::serde::decode_owned_from_slice(&buffer, oxicode::config::standard())?;
let start = Instant::now();
let mut _sum = 0.0;
for i in 0..10 {
for j in 0..10 {
_sum += loaded_traditional[[i, j]];
}
}
let traditional_access_time = start.elapsed();
println!("\nPerformance Results:");
println!(" File sizes:");
println!(
" Zero-copy: {:.2} MB",
zero_copy_path.metadata()?.len() as f64 / (1024.0 * 1024.0)
);
println!(
" Traditional: {:.2} MB",
traditional_path.metadata()?.len() as f64 / (1024.0 * 1024.0)
);
println!("\n Serialization times:");
println!(" Zero-copy: {:?}", zero_copy_save_time);
println!(" Traditional: {:?}", traditional_save_time);
println!("\n Deserialization times:");
println!(" Zero-copy: {:?}", zero_copy_load_time);
println!(" Traditional: {:?}", traditional_load_time);
println!("\n Array access times (10x10 block):");
println!(" Zero-copy: {:?}", zero_copy_access_time);
println!(" Traditional: {:?}", traditional_access_time);
println!("\nPerformance summary:");
let ser_ratio =
zero_copy_save_time.as_micros() as f64 / traditional_save_time.as_micros() as f64;
if ser_ratio < 1.0 {
println!(
" Zero-copy serialization is {:.2}x faster than traditional",
1.0 / ser_ratio
);
} else {
println!(
" Traditional serialization is {:.2}x faster than zero-copy",
ser_ratio
);
}
let deser_ratio =
zero_copy_load_time.as_micros() as f64 / traditional_load_time.as_micros() as f64;
if deser_ratio < 1.0 {
println!(
" Zero-copy deserialization is {:.2}x faster than traditional",
1.0 / deser_ratio
);
} else {
println!(
" Traditional deserialization is {:.2}x faster than zero-copy",
deser_ratio
);
}
let access_ratio =
zero_copy_access_time.as_micros() as f64 / traditional_access_time.as_micros() as f64;
if access_ratio < 1.0 {
println!(
" Zero-copy access is {:.2}x faster than traditional",
1.0 / access_ratio
);
} else {
println!(
" Traditional access is {:.2}x faster than zero-copy",
access_ratio
);
}
println!("\nNote: Zero-copy serialization's main advantage is with extremely large arrays that don't fit in memory");
Ok(())
}
#[allow(dead_code)]
fn updating_data_example(tempdir: &Path) -> Result<(), Box<dyn std::error::Error>> {
println!("\n5. Updating Data Example");
println!("------------------------");
let data = Array2::<f32>::from_shape_fn((10, 10), |(i, j)| (i * 10 + j) as f32);
println!("Created a 10x10 array");
let file_path = tempdir.join("updateable_array.bin");
MemoryMappedArray::<f32>::save_array(&data, &file_path, None)?;
println!("Saved initial array to file");
println!("\nOriginal array (first 5x5 corner):");
for i in 0..5 {
let mut row = String::new();
for j in 0..5 {
row.push_str(&format!("{:4.0} ", data[[i, j]]));
}
println!(" {}", row);
}
let mut mmap = MemoryMappedArray::<f32>::open_zero_copy(&file_path, AccessMode::ReadWrite)?;
println!("\nLoaded array with read-write access for updating");
{
let mut array = mmap.as_array_mut::<scirs2_core::ndarray::Ix2>()?;
for i in 0..10 {
array[[i, i]] = 1000.0;
}
for i in 0..5 {
for j in 5..10 {
array[[i, j]] = -1.0;
}
}
}
mmap.flush()?;
println!("Modified array and flushed changes to disk");
let loaded = MemoryMappedArray::<f32>::open_zero_copy(&file_path, AccessMode::ReadOnly)?;
let loaded_array = loaded.readonlyarray::<scirs2_core::ndarray::Ix2>()?;
println!("\nModified array (10x10):");
for i in 0..10 {
let mut row = String::new();
for j in 0..10 {
row.push_str(&format!("{:5.0} ", loaded_array[[i, j]]));
}
println!(" {}", row);
}
Ok(())
}