use excelstream::fast_writer::UltraLowMemoryWorkbook;
use excelstream::types::{CellStyle, CellValue};
use excelstream::writer::ExcelWriter;
use std::time::Instant;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== ExcelStream Writers Performance Comparison ===\n");
const NUM_ROWS: usize = 1_000_000; const NUM_COLS: usize = 30;
println!("Test configuration:");
println!("- Rows: {:?}", NUM_ROWS);
println!("- Columns: {} (mixed data types)", NUM_COLS);
println!("- Data types: String, Int, Float, Date, Email, URL, etc.");
println!(
"- Excel limit: 1,048,576 rows per sheet (we're using {})",
NUM_ROWS
);
println!(
"- Total cells: {} million",
(NUM_ROWS * NUM_COLS * 4) / 1_000_000
);
println!(
"- Estimated time: {} minutes",
if NUM_ROWS >= 1_000_000 {
"15-40"
} else {
"2-5"
}
);
println!(
"- Expected file size: ~{} MB each\n",
if NUM_ROWS >= 1_000_000 {
"175-180"
} else {
"18-20"
}
);
println!("1. ExcelWriter.write_row() - String-based:");
let start = Instant::now();
test_write_row_strings("test_strings.xlsx", NUM_ROWS, NUM_COLS)?;
let duration1 = start.elapsed();
let speed1 = NUM_ROWS as f64 / duration1.as_secs_f64();
println!(" Time: {:?}", duration1);
println!(" Speed: {:.0} rows/sec\n", speed1);
println!("2. ExcelWriter.write_row_typed() - Typed values:");
let start = Instant::now();
test_write_row_typed("test_typed.xlsx", NUM_ROWS, NUM_COLS)?;
let duration2 = start.elapsed();
let speed2 = NUM_ROWS as f64 / duration2.as_secs_f64();
println!(" Time: {:?}", duration2);
println!(" Speed: {:.0} rows/sec\n", speed2);
println!("3. ExcelWriter.write_row_styled() - With styling:");
let start = Instant::now();
test_write_row_styled("test_styled.xlsx", NUM_ROWS, NUM_COLS)?;
let duration3 = start.elapsed();
let speed3 = NUM_ROWS as f64 / duration3.as_secs_f64();
println!(" Time: {:?}", duration3);
println!(" Speed: {:.0} rows/sec\n", speed3);
println!("4. UltraLowMemoryWorkbook - Direct low-level API:");
let start = Instant::now();
test_ultra_low_memory("test_ultra.xlsx", NUM_ROWS, NUM_COLS)?;
let duration4 = start.elapsed();
let speed4 = NUM_ROWS as f64 / duration4.as_secs_f64();
println!(" Time: {:?}", duration4);
println!(" Speed: {:.0} rows/sec", speed4);
println!(" Note: Slower due to Vec<String> → Vec<&str> conversion overhead\n");
println!("=== Performance Analysis ===");
println!(
"ExcelWriter.write_row(): {:.0} rows/sec (1.00x) [baseline]",
speed1
);
println!(
"ExcelWriter.write_row_typed(): {:.0} rows/sec ({:.2}x)",
speed2,
speed2 / speed1
);
println!(
"ExcelWriter.write_row_styled(): {:.0} rows/sec ({:.2}x)",
speed3,
speed3 / speed1
);
println!(
"UltraLowMemoryWorkbook: {:.0} rows/sec ({:.2}x)",
speed4,
speed4 / speed1
);
println!();
let diff2 = ((speed2 - speed1) / speed1 * 100.0).round();
let diff3 = ((speed3 - speed1) / speed1 * 100.0).round();
let diff4 = ((speed4 - speed1) / speed1 * 100.0).round();
println!("=== Speed Comparison vs ExcelWriter.write_row() ===");
println!("ExcelWriter.write_row_typed():");
if diff2 > 0.0 {
println!(" +{:.0}% faster", diff2);
} else if diff2 < 0.0 {
println!(" {:.0}% slower (type conversion overhead)", diff2.abs());
} else {
println!(" Same speed");
}
println!("\nExcelWriter.write_row_styled():");
if diff3 > 0.0 {
println!(" +{:.0}% faster", diff3);
} else if diff3 < 0.0 {
println!(" {:.0}% slower (styling overhead)", diff3.abs());
} else {
println!(" Same speed");
}
println!("\nUltraLowMemoryWorkbook:");
if diff4 > 0.0 {
println!(" +{:.0}% faster ⚡", diff4);
} else {
println!(" {:.0}% slower", diff4.abs());
}
println!();
println!("=== Feature Comparison ===");
println!("┌─────────────────────┬──────────────┬──────────────┬──────────────┬──────────────┐");
println!("│ Feature │ write_row() │ typed() │ styled() │ UltraLowMem │");
println!("├─────────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤");
println!(
"│ Speed │ Baseline │ {:<12} │ {:<12} │ {:<12} │",
format!("{:+.0}%", diff2),
format!("{:+.0}%", diff3),
format!("{:+.0}%", diff4)
);
println!("│ Excel Formulas │ ❌ No │ ✅ Yes │ ✅ Yes │ ❌ No │");
println!("│ Number Types │ ❌ Text │ ✅ Number │ ✅ Number │ ❌ Text │");
println!("│ Boolean Display │ ❌ text │ ✅ TRUE/FALSE│ ✅ TRUE/FALSE│ ❌ text │");
println!("│ Cell Styling │ ❌ No │ ❌ No │ ✅ Yes │ ❌ No │");
println!("│ API Simplicity │ ✅ Simple │ ✅ Simple │ ✅ Simple │ ✅ Simple │");
println!("│ Multi-sheet │ ✅ Yes │ ✅ Yes │ ✅ Yes │ ✅ Yes │");
println!("│ Memory Efficient │ ⚠️ Medium │ ⚠️ Medium │ ⚠️ Medium │ ✅ High │");
println!("│ Large Datasets │ ⚠️ OK │ ⚠️ OK │ ⚠️ OK │ ✅ Best │");
println!("└─────────────────────┴──────────────┴──────────────┴──────────────┴──────────────┘");
println!();
println!("=== Recommendations ===");
println!("✅ Use ExcelWriter.write_row() when:");
println!(" - Simple text export");
println!(" - All data already strings");
println!(" - Don't need Excel formulas or styling");
println!(" - Standard use cases");
println!(" - Performance: {:.0} rows/sec", speed1);
println!();
println!("✅ Use ExcelWriter.write_row_typed() when:");
println!(" - Need Excel formulas (SUM, AVERAGE, etc.)");
println!(" - Want proper number/boolean types");
println!(" - Users will do calculations in Excel");
println!(" - Mixed data types");
println!(" - Performance: {:.0} rows/sec ({:+.0}%)", speed2, diff2);
println!();
println!("✅ Use ExcelWriter.write_row_styled() when:");
println!(" - Need cell formatting (colors, bold, borders)");
println!(" - Visual emphasis on important data");
println!(" - Professional reports and dashboards");
println!(" - Highlighting patterns or anomalies");
println!(
" - Performance: {:.0} rows/sec ({:+.0}%) ⚡ FASTEST!",
speed3, diff3
);
println!();
println!("✅ Use UltraLowMemoryWorkbook direct when:");
println!(" - Have data already as &str (avoid String allocation)");
println!(" - Need lowest-level control");
println!(" - Building custom abstractions");
println!(" - Memory-constrained environments");
println!(" - Note: This test shows slower due to Vec<String>→Vec<&str> conversion");
println!(" - In real usage with &str data, UltraLowMemoryWorkbook is fastest!");
println!(" - Performance: {:.0} rows/sec ({:+.0}%)", speed4, diff4);
println!();
println!("💡 Key Insight:");
println!(" - ExcelWriter already uses UltraLowMemoryWorkbook internally!");
println!(" - write_row_styled() is fastest because it avoids string conversions");
println!(" - Use styled() for best performance + features");
Ok(())
}
fn test_write_row_strings(
filename: &str,
num_rows: usize,
num_cols: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let mut writer = ExcelWriter::new(filename)?;
let headers = vec![
"ID",
"Name",
"Email",
"Age",
"Salary",
"Active",
"Score",
"Department",
"Join_Date",
"Phone",
"Address",
"City",
"Country",
"Postal_Code",
"Website",
"Tax_ID",
"Credit_Limit",
"Balance",
"Last_Login",
"Status",
"Notes",
"Created_At",
"Updated_At",
"Version",
"Priority",
"Category",
"Tags",
"Description",
"Metadata",
"Reference",
];
writer.write_header(&headers[..num_cols])?;
for i in 1..=num_rows {
let row = generate_mixed_row_strings(i, num_cols);
let row_refs: Vec<&str> = row.iter().map(|s| s.as_str()).collect();
writer.write_row(&row_refs)?;
}
writer.save()?;
Ok(())
}
fn test_write_row_typed(
filename: &str,
num_rows: usize,
num_cols: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let mut writer = ExcelWriter::new(filename)?;
let headers = vec![
"ID",
"Name",
"Email",
"Age",
"Salary",
"Active",
"Score",
"Department",
"Join_Date",
"Phone",
"Address",
"City",
"Country",
"Postal_Code",
"Website",
"Tax_ID",
"Credit_Limit",
"Balance",
"Last_Login",
"Status",
"Notes",
"Created_At",
"Updated_At",
"Version",
"Priority",
"Category",
"Tags",
"Description",
"Metadata",
"Reference",
];
writer.write_header(&headers[..num_cols])?;
for i in 1..=num_rows {
let row = generate_mixed_row_typed(i, num_cols);
writer.write_row_typed(&row[..num_cols])?;
}
writer.save()?;
Ok(())
}
fn test_write_row_styled(
filename: &str,
num_rows: usize,
num_cols: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let mut writer = ExcelWriter::new(filename)?;
let headers = vec![
"ID",
"Name",
"Email",
"Age",
"Salary",
"Active",
"Score",
"Department",
"Join_Date",
"Phone",
"Address",
"City",
"Country",
"Postal_Code",
"Website",
"Tax_ID",
"Credit_Limit",
"Balance",
"Last_Login",
"Status",
"Notes",
"Created_At",
"Updated_At",
"Version",
"Priority",
"Category",
"Tags",
"Description",
"Metadata",
"Reference",
];
let header_cells: Vec<(CellValue, CellStyle)> = headers[..num_cols]
.iter()
.map(|h| (CellValue::String(h.to_string()), CellStyle::HeaderBold))
.collect();
writer.write_row_styled(&header_cells)?;
for i in 1..=num_rows {
let row = generate_mixed_row_styled(i, num_cols);
writer.write_row_styled(&row[..num_cols])?;
}
writer.save()?;
Ok(())
}
fn test_ultra_low_memory(
filename: &str,
num_rows: usize,
num_cols: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let mut workbook = UltraLowMemoryWorkbook::new(filename)?;
workbook.add_worksheet("Sheet1")?;
let headers = vec![
"ID",
"Name",
"Email",
"Age",
"Salary",
"Active",
"Score",
"Department",
"Join_Date",
"Phone",
"Address",
"City",
"Country",
"Postal_Code",
"Website",
"Tax_ID",
"Credit_Limit",
"Balance",
"Last_Login",
"Status",
"Notes",
"Created_At",
"Updated_At",
"Version",
"Priority",
"Category",
"Tags",
"Description",
"Metadata",
"Reference",
];
workbook.write_row(&headers[..num_cols])?;
for i in 1..=num_rows {
let row = generate_mixed_row_strings(i, num_cols);
let row_refs: Vec<&str> = row.iter().map(|s| s.as_str()).collect(); workbook.write_row(&row_refs)?;
}
workbook.close()?;
Ok(())
}
fn generate_mixed_row_strings(row_num: usize, num_cols: usize) -> Vec<String> {
let mut row = Vec::with_capacity(num_cols);
for col in 0..num_cols {
let value = match col {
0 => format!("{}", row_num), 1 => format!("User_{}", row_num), 2 => format!("user{}@example.com", row_num), 3 => format!("{}", 20 + (row_num % 50)), 4 => format!("{:.2}", 30000.0 + (row_num as f64 * 123.45)), 5 => if row_num.is_multiple_of(2) {
"true"
} else {
"false"
}
.to_string(), 6 => format!("{:.1}", 50.0 + (row_num % 50) as f64), 7 => match row_num % 5 {
0 => "Engineering",
1 => "Sales",
2 => "Marketing",
3 => "HR",
_ => "Operations",
}
.to_string(),
8 => format!("2024-{:02}-{:02}", 1 + (row_num % 12), 1 + (row_num % 28)),
9 => format!("+1-555-{:04}-{:04}", row_num % 1000, row_num % 10000),
10 => format!("{} Main Street", row_num),
11 => match row_num % 10 {
0 => "New York",
1 => "Los Angeles",
2 => "Chicago",
3 => "Houston",
4 => "Phoenix",
5 => "Philadelphia",
6 => "San Antonio",
7 => "San Diego",
8 => "Dallas",
_ => "San Jose",
}
.to_string(),
12 => "USA".to_string(),
13 => format!("{:05}", 10000 + (row_num % 90000)),
14 => format!("https://example{}.com", row_num),
15 => format!("TAX-{:08}", row_num),
16 => format!("{:.2}", 5000.0 + (row_num as f64 * 50.0)),
17 => format!("{:.2}", (row_num as f64 * 12.34) % 10000.0),
18 => format!(
"2024-12-{:02} {:02}:{:02}:{:02}",
1 + (row_num % 28),
row_num % 24,
row_num % 60,
row_num % 60
),
19 => match row_num % 4 {
0 => "Active",
1 => "Pending",
2 => "Suspended",
_ => "Inactive",
}
.to_string(),
20 => format!("Note for record #{}", row_num),
21 => format!(
"2024-01-01T{:02}:{:02}:{:02}Z",
row_num % 24,
row_num % 60,
row_num % 60
),
22 => format!(
"2024-12-01T{:02}:{:02}:{:02}Z",
row_num % 24,
row_num % 60,
row_num % 60
),
23 => format!("v{}.{}.{}", row_num % 10, row_num % 100, row_num % 1000),
24 => match row_num % 3 {
0 => "High",
1 => "Medium",
_ => "Low",
}
.to_string(),
25 => match row_num % 6 {
0 => "Category A",
1 => "Category B",
2 => "Category C",
3 => "Category D",
4 => "Category E",
_ => "Category F",
}
.to_string(),
26 => format!("tag1,tag{},tag{}", row_num % 10, row_num % 20),
27 => format!(
"Description for record {} with some longer text to test performance",
row_num
),
28 => format!("{{\"key\":\"{}\",\"value\":{}}}", row_num, row_num * 100),
_ => format!("REF-{:08}", row_num),
};
row.push(value);
}
row
}
fn generate_mixed_row_typed(row_num: usize, num_cols: usize) -> Vec<CellValue> {
let mut row = Vec::with_capacity(num_cols);
for col in 0..num_cols {
let value = match col {
0 => CellValue::Int(row_num as i64), 1 => CellValue::String(format!("User_{}", row_num)), 2 => CellValue::String(format!("user{}@example.com", row_num)), 3 => CellValue::Int((20 + (row_num % 50)) as i64), 4 => CellValue::Float(30000.0 + (row_num as f64 * 123.45)), 5 => CellValue::Bool(row_num.is_multiple_of(2)), 6 => CellValue::Float(50.0 + (row_num % 50) as f64), 7 => CellValue::String(
match row_num % 5 {
0 => "Engineering",
1 => "Sales",
2 => "Marketing",
3 => "HR",
_ => "Operations",
}
.to_string(),
),
8 => CellValue::String(format!(
"2024-{:02}-{:02}",
1 + (row_num % 12),
1 + (row_num % 28)
)),
9 => CellValue::String(format!(
"+1-555-{:04}-{:04}",
row_num % 1000,
row_num % 10000
)),
10 => CellValue::String(format!("{} Main Street", row_num)),
11 => CellValue::String(
match row_num % 10 {
0 => "New York",
1 => "Los Angeles",
2 => "Chicago",
3 => "Houston",
4 => "Phoenix",
5 => "Philadelphia",
6 => "San Antonio",
7 => "San Diego",
8 => "Dallas",
_ => "San Jose",
}
.to_string(),
),
12 => CellValue::String("USA".to_string()),
13 => CellValue::Int((10000 + (row_num % 90000)) as i64),
14 => CellValue::String(format!("https://example{}.com", row_num)),
15 => CellValue::String(format!("TAX-{:08}", row_num)),
16 => CellValue::Float(5000.0 + (row_num as f64 * 50.0)),
17 => CellValue::Float((row_num as f64 * 12.34) % 10000.0),
18 => CellValue::String(format!(
"2024-12-{:02} {:02}:{:02}:{:02}",
1 + (row_num % 28),
row_num % 24,
row_num % 60,
row_num % 60
)),
19 => CellValue::String(
match row_num % 4 {
0 => "Active",
1 => "Pending",
2 => "Suspended",
_ => "Inactive",
}
.to_string(),
),
20 => CellValue::String(format!("Note for record #{}", row_num)),
21 => CellValue::String(format!(
"2024-01-01T{:02}:{:02}:{:02}Z",
row_num % 24,
row_num % 60,
row_num % 60
)),
22 => CellValue::String(format!(
"2024-12-01T{:02}:{:02}:{:02}Z",
row_num % 24,
row_num % 60,
row_num % 60
)),
23 => CellValue::String(format!(
"v{}.{}.{}",
row_num % 10,
row_num % 100,
row_num % 1000
)),
24 => CellValue::String(
match row_num % 3 {
0 => "High",
1 => "Medium",
_ => "Low",
}
.to_string(),
),
25 => CellValue::String(
match row_num % 6 {
0 => "Category A",
1 => "Category B",
2 => "Category C",
3 => "Category D",
4 => "Category E",
_ => "Category F",
}
.to_string(),
),
26 => CellValue::String(format!("tag1,tag{},tag{}", row_num % 10, row_num % 20)),
27 => CellValue::String(format!(
"Description for record {} with some longer text to test performance",
row_num
)),
28 => CellValue::String(format!(
"{{\"key\":\"{}\",\"value\":{}}}",
row_num,
row_num * 100
)),
_ => CellValue::String(format!("REF-{:08}", row_num)),
};
row.push(value);
}
row
}
fn generate_mixed_row_styled(row_num: usize, num_cols: usize) -> Vec<(CellValue, CellStyle)> {
let mut row = Vec::with_capacity(num_cols);
for col in 0..num_cols {
let (value, style) = match col {
0 => (CellValue::Int(row_num as i64), CellStyle::Default), 1 => (
CellValue::String(format!("User_{}", row_num)),
CellStyle::Default,
), 2 => (
CellValue::String(format!("user{}@example.com", row_num)),
CellStyle::Default,
), 3 => (
CellValue::Int((20 + (row_num % 50)) as i64),
CellStyle::NumberInteger,
), 4 => {
let salary = 30000.0 + (row_num as f64 * 123.45);
(CellValue::Float(salary), CellStyle::NumberCurrency)
} 5 => (
CellValue::Bool(row_num.is_multiple_of(2)),
CellStyle::Default,
), 6 => {
let score = 50.0 + (row_num % 50) as f64;
let style = if score > 75.0 {
CellStyle::HighlightGreen
} else if score < 60.0 {
CellStyle::HighlightRed
} else {
CellStyle::NumberDecimal
};
(CellValue::Float(score), style)
} 7 => (
CellValue::String(
match row_num % 5 {
0 => "Engineering",
1 => "Sales",
2 => "Marketing",
3 => "HR",
_ => "Operations",
}
.to_string(),
),
CellStyle::Default,
),
8 => (
CellValue::String(format!(
"2024-{:02}-{:02}",
1 + (row_num % 12),
1 + (row_num % 28)
)),
CellStyle::DateDefault,
),
9 => (
CellValue::String(format!(
"+1-555-{:04}-{:04}",
row_num % 1000,
row_num % 10000
)),
CellStyle::Default,
),
10 => (
CellValue::String(format!("{} Main Street", row_num)),
CellStyle::Default,
),
11 => (
CellValue::String(
match row_num % 10 {
0 => "New York",
1 => "Los Angeles",
2 => "Chicago",
3 => "Houston",
4 => "Phoenix",
5 => "Philadelphia",
6 => "San Antonio",
7 => "San Diego",
8 => "Dallas",
_ => "San Jose",
}
.to_string(),
),
CellStyle::Default,
),
12 => (CellValue::String("USA".to_string()), CellStyle::Default),
13 => (
CellValue::Int((10000 + (row_num % 90000)) as i64),
CellStyle::Default,
),
14 => (
CellValue::String(format!("https://example{}.com", row_num)),
CellStyle::Default,
),
15 => (
CellValue::String(format!("TAX-{:08}", row_num)),
CellStyle::Default,
),
16 => (
CellValue::Float(5000.0 + (row_num as f64 * 50.0)),
CellStyle::NumberCurrency,
),
17 => (
CellValue::Float((row_num as f64 * 12.34) % 10000.0),
CellStyle::NumberCurrency,
),
18 => (
CellValue::String(format!(
"2024-12-{:02} {:02}:{:02}:{:02}",
1 + (row_num % 28),
row_num % 24,
row_num % 60,
row_num % 60
)),
CellStyle::DateTimestamp,
),
19 => {
let status = match row_num % 4 {
0 => "Active",
1 => "Pending",
2 => "Suspended",
_ => "Inactive",
};
let style = match status {
"Active" => CellStyle::HighlightGreen,
"Pending" => CellStyle::HighlightYellow,
_ => CellStyle::Default,
};
(CellValue::String(status.to_string()), style)
}
20 => (
CellValue::String(format!("Note for record #{}", row_num)),
CellStyle::Default,
),
21 => (
CellValue::String(format!(
"2024-01-01T{:02}:{:02}:{:02}Z",
row_num % 24,
row_num % 60,
row_num % 60
)),
CellStyle::Default,
),
22 => (
CellValue::String(format!(
"2024-12-01T{:02}:{:02}:{:02}Z",
row_num % 24,
row_num % 60,
row_num % 60
)),
CellStyle::Default,
),
23 => (
CellValue::String(format!(
"v{}.{}.{}",
row_num % 10,
row_num % 100,
row_num % 1000
)),
CellStyle::Default,
),
24 => {
let priority = match row_num % 3 {
0 => "High",
1 => "Medium",
_ => "Low",
};
let style = match priority {
"High" => CellStyle::TextBold,
_ => CellStyle::Default,
};
(CellValue::String(priority.to_string()), style)
}
25 => (
CellValue::String(
match row_num % 6 {
0 => "Category A",
1 => "Category B",
2 => "Category C",
3 => "Category D",
4 => "Category E",
_ => "Category F",
}
.to_string(),
),
CellStyle::Default,
),
26 => (
CellValue::String(format!("tag1,tag{},tag{}", row_num % 10, row_num % 20)),
CellStyle::Default,
),
27 => (
CellValue::String(format!(
"Description for record {} with some longer text to test performance",
row_num
)),
CellStyle::Default,
),
28 => (
CellValue::String(format!(
"{{\"key\":\"{}\",\"value\":{}}}",
row_num,
row_num * 100
)),
CellStyle::Default,
),
_ => (
CellValue::String(format!("REF-{:08}", row_num)),
CellStyle::Default,
),
};
row.push((value, style));
}
row
}