Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions diskann-benchmark-runner/src/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,6 @@ impl Any {
}
}

/// Used in `DispatchRule::description(f, _)` to ensure that additional description
/// lines are properly aligned.
#[macro_export]
macro_rules! describeln {
($writer:ident, $fmt:literal) => {
writeln!($writer, concat!(" ", $fmt))
};
($writer:ident, $fmt:literal, $($args:expr),* $(,)?) => {
writeln!($writer, concat!(" ", $fmt), $($args,)*)
};
}

trait SerializableAny: std::fmt::Debug {
fn as_any(&self) -> &dyn std::any::Any;
fn dump(&self) -> Result<serde_json::Value, serde_json::Error>;
Expand Down
23 changes: 8 additions & 15 deletions diskann-benchmark-runner/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ use crate::{
output::Output,
registry,
result::Checkpoint,
utils::fmt::Banner,
utils::fmt::{Banner, Indent},
};

/// Check if we're running in debug mode and error if not allowed.
Expand Down Expand Up @@ -227,14 +227,12 @@ impl App {
Commands::Benchmarks {} => {
writeln!(output, "Registered Benchmarks:")?;
for (name, description) in benchmarks.names() {
let mut lines = description.lines();
if let Some(first) = lines.next() {
writeln!(output, " {}: {}", name, first)?;
for line in lines {
writeln!(output, " {}", line)?;
}
write!(output, " {name}:")?;
if description.is_empty() {
writeln!(output)?;
} else {
writeln!(output, " {}: <no description>", name)?;
writeln!(output)?;
write!(output, "{}", Indent::new(&description, 8))?;
}
}
}
Expand Down Expand Up @@ -264,13 +262,8 @@ impl App {
)?;
writeln!(output, "Closest matches:\n")?;
for (i, mismatch) in mismatches.into_iter().enumerate() {
writeln!(
output,
" {}. \"{}\": {}",
i + 1,
mismatch.method(),
mismatch.reason(),
)?;
writeln!(output, " {}. \"{}\":", i + 1, mismatch.method(),)?;
writeln!(output, "{}", Indent::new(mismatch.reason(), 8),)?;
}
writeln!(output)?;

Expand Down
269 changes: 269 additions & 0 deletions diskann-benchmark-runner/src/utils/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,186 @@ impl std::fmt::Display for Banner<'_> {
}
}

////////////
// Indent //
////////////

/// Indents each line of a string by a fixed number of spaces.
///
/// Each line is prefixed with `spaces` spaces and terminated with a newline.
///
/// # Examples
///
/// ```
/// use diskann_benchmark_runner::utils::fmt::Indent;
///
/// let indented = Indent::new("hello\nworld", 4).to_string();
/// assert_eq!(indented, " hello\n world\n");
/// ```
#[derive(Debug, Clone, Copy)]
pub struct Indent<'a> {
string: &'a str,
spaces: usize,
}

impl<'a> Indent<'a> {
/// Create a new [`Indent`] that will prefix each line of `string` with `spaces` spaces.
pub fn new(string: &'a str, spaces: usize) -> Self {
Self { string, spaces }
}
}

impl std::fmt::Display for Indent<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let spaces = self.spaces;
self.string
.lines()
.try_for_each(|ln| writeln!(f, "{: >spaces$}{}", "", ln))
}
}

/////////////
// Delimit //
/////////////

/// Formats an iterator with a delimiter between items and optional overrides for
/// the final delimiter and pair formatting.
///
/// This is a single-use wrapper: the iterator is consumed on the first call to [`Display::fmt`].
/// Subsequent calls will print `<missing>`.
///
/// Use [`Delimit::with_last`] to change the delimiter before the final item
/// (e.g., `", and "`), which is useful for natural-language lists like
/// `"a, b, and c"`.
///
/// Use [`Delimit::with_pair`] to change formatting when there are only two items.
///
/// # Examples
///
/// ```
/// use diskann_benchmark_runner::utils::fmt::Delimit;
///
/// let d = Delimit::new(["a", "b", "c"], ", ").with_last(", and ");
/// assert_eq!(d.to_string(), "a, b, and c");
///
/// let d = Delimit::new(["a", "b"], ", ").with_last(", and ");
/// assert_eq!(d.to_string(), "a, and b");
///
/// let d = Delimit::new(["a", "b"], ", ")
/// .with_last(", and ")
/// .with_pair(" and ");
/// assert_eq!(d.to_string(), "a and b");
/// ```
pub struct Delimit<'a, I> {
itr: std::cell::Cell<Option<I>>,
delimiter: &'a str,
last: &'a str,
pair: Option<&'a str>,
}

impl<'a, I> Delimit<'a, I> {
/// Create a new [`Delimit`] from an iterable and a delimiter.
///
/// By default, the same delimiter is used between every item. Use
/// [`Self::with_last`] and [`Self::with_pair`] to opt into special handling
/// before the final item or for pairs.
pub fn new(itr: impl IntoIterator<IntoIter = I>, delimiter: &'a str) -> Self {
Self {
itr: std::cell::Cell::new(Some(itr.into_iter())),
delimiter,
last: delimiter,
pair: None,
}
}

/// Use `last` before the final item when formatting three or more items.
pub fn with_last(mut self, last: &'a str) -> Self {
self.last = last;
self
}

/// Use `pair` when formatting exactly two items.
pub fn with_pair(mut self, pair: &'a str) -> Self {
self.pair = Some(pair);
self
}
}

impl<I> std::fmt::Display for Delimit<'_, I>
where
I: Iterator<Item: std::fmt::Display>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Some(mut itr) = self.itr.take() else {
return write!(f, "<missing>");
};

let mut count = 0;
let mut current = if let Some(item) = itr.next() {
item
} else {
// Empty iterator
return Ok(());
};

loop {
match itr.next() {
None => {
// "current" is the last item. If it is also the first, we write it
// directly.
//
// Otherwise, we check if we've just emitted a single item so far and
// use `pair` if available. Otherwise, we try `last` and finally
// `delimiter`.
let delimiter = if count == 0 {
""
} else if count == 1 {
self.pair.unwrap_or(self.last)
} else {
self.last
};

return write!(f, "{}{}", delimiter, current);
}
Some(next) => {
// There is at least one item next. We print "current" and move on.
let delimiter = if count == 0 { "" } else { self.delimiter };

write!(f, "{}{}", delimiter, current)?;
count += 1;
current = next;
}
}
}
}
}

///////////
// Quote //
///////////

/// Wraps a value in double quotes when displayed.
///
/// # Examples
///
/// ```
/// use diskann_benchmark_runner::utils::fmt::Quote;
///
/// assert_eq!(Quote("hello").to_string(), "\"hello\"");
/// assert_eq!(Quote(42).to_string(), "\"42\"");
/// ```
#[derive(Debug, Clone, Copy)]
pub struct Quote<T>(pub T);

impl<T> std::fmt::Display for Quote<T>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.0)
}
}

///////////
// Tests //
///////////
Expand Down Expand Up @@ -327,4 +507,93 @@ string, , string
let mut row = table.row(0);
row.insert(1, 3);
}

#[test]
fn test_indent_single_line() {
let s = Indent::new("hello", 4).to_string();
assert_eq!(s, " hello\n");
}

#[test]
fn test_indent_multi_line() {
let s = Indent::new("hello\nworld\nfoo", 2).to_string();
assert_eq!(s, " hello\n world\n foo\n");
}

#[test]
fn test_indent_zero_spaces() {
let s = Indent::new("hello\nworld", 0).to_string();
assert_eq!(s, "hello\nworld\n");
}

#[test]
fn test_indent_empty_string() {
let s = Indent::new("", 4).to_string();
assert_eq!(s, "");
}

#[test]
fn test_delimit_empty() {
let d = Delimit::new(std::iter::empty::<&str>(), ", ");
assert_eq!(d.to_string(), "");
}

#[test]
fn test_delimit_single_item() {
let d = Delimit::new(["a"], ", ").with_last(", and ");
assert_eq!(d.to_string(), "a");
}

#[test]
fn test_delimit_two_items_with_last() {
let d = Delimit::new(["a", "b"], ", ").with_last(", and ");
assert_eq!(d.to_string(), "a, and b");
}

#[test]
fn test_delimit_two_items_with_pair() {
let d = Delimit::new(["a", "b"], ", ")
.with_last(", and ")
.with_pair(" and ");
assert_eq!(d.to_string(), "a and b");
}

#[test]
fn test_delimit_three_items_with_last() {
let d = Delimit::new(["a", "b", "c"], ", ")
.with_last(", and ")
.with_pair(" and ");
assert_eq!(d.to_string(), "a, b, and c");
}

#[test]
fn test_delimit_without_last() {
let d = Delimit::new(["x", "y", "z"], " | ");
assert_eq!(d.to_string(), "x | y | z");
}

#[test]
fn test_delimit_second_display_prints_missing() {
let d = Delimit::new(["a", "b"], ", ");
assert_eq!(d.to_string(), "a, b");
assert_eq!(d.to_string(), "<missing>");
}

#[test]
fn test_quote() {
assert_eq!(Quote("hello").to_string(), "\"hello\"");
}

#[test]
fn test_quote_with_integer() {
assert_eq!(Quote(42).to_string(), "\"42\"");
}

#[test]
fn test_delimit_with_quote() {
let d = Delimit::new(["topk", "range"].iter().map(Quote), ", ")
.with_last(", and ")
.with_pair(" and ");
assert_eq!(d.to_string(), "\"topk\" and \"range\"");
}
}
15 changes: 10 additions & 5 deletions diskann-benchmark-runner/tests/benchmark/test-4/stdout.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
Registered Benchmarks:
type-bench-f32: tag "test-input-types"
type-bench-f32:
tag "test-input-types"
float32
type-bench-i8: tag "test-input-types"
type-bench-i8:
tag "test-input-types"
int8
exact-type-bench-f32-1000: tag "test-input-types"
exact-type-bench-f32-1000:
tag "test-input-types"
float32, dim=1000
simple-bench: tag "test-input-dim"
simple-bench:
tag "test-input-dim"
dim=None only
dim-bench: tag "test-input-dim"
dim-bench:
tag "test-input-dim"
matches all
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Benchmarking in debug mode produces misleading performance results.
Please compile in release mode or use the --allow-debug flag to bypass this check.
Please compile in release mode or use the --allow-debug flag to bypass this check.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ Could not find a match for the following input:

Closest matches:

1. "type-bench-f32": expected "float32" but found "float16"
2. "type-bench-i8": expected "int8" but found "float16"
3. "exact-type-bench-f32-1000": expected "float32" but found "float16"; expected dim=1000, but found dim=128
1. "type-bench-f32":
expected "float32" but found "float16"

2. "type-bench-i8":
expected "int8" but found "float16"

3. "exact-type-bench-f32-1000":
expected "float32" but found "float16"; expected dim=1000, but found dim=128


could not find a benchmark for all inputs
Loading
Loading