Current section

Files

Jump to
aarondb src bm25_benchmark.gleam
Raw

src/bm25_benchmark.gleam

import aarondb/fact
import aarondb/index/bm25
import gleam/int
import gleam/io
import gleam/list
/// Reproducible local evidence harness for the caller-owned BM25 primitive.
///
/// Run with `gleam run -m bm25_benchmark`. This measures one BEAM process,
/// one immutable in-memory index, and sequential operations. It does not
/// claim persistence, transaction integration, or concurrent mutation safety.
pub fn main() {
let document_count = 1000
let query_count = 100
let documents = documents(document_count)
let build_start = now()
let index =
list.fold(documents, bm25.empty("body"), fn(index, document) {
let #(entity, text) = document
bm25.add(index, entity, text)
})
let build_ns = now() - build_start
let query_times =
list.repeat(Nil, query_count)
|> list.map(fn(_) {
let start = now()
let _ = bm25.search(index, "gleam database retrieval", 1.2, 0.75, 10)
nanoseconds_to_milliseconds(now() - start)
})
let update_start = now()
let _ =
list.fold(documents, index, fn(current, document) {
let #(entity, _) = document
bm25.add(current, entity, "gleam updated document retrieval")
})
let update_ns = now() - update_start
io.println("document_count=" <> int.to_string(document_count))
io.println("query_count=" <> int.to_string(query_count))
io.println(
"build_ms=" <> int.to_string(nanoseconds_to_milliseconds(build_ns)),
)
io.println(
"replace_all_ms=" <> int.to_string(nanoseconds_to_milliseconds(update_ns)),
)
print_latency("search", query_times)
}
fn documents(count: Int) -> List(#(fact.EntityId, String)) {
list.repeat(Nil, count)
|> list.index_map(fn(_, offset) {
let id = offset + 1
#(
fact.EntityId(id),
"gleam database retrieval document " <> int.to_string(id),
)
})
}
fn print_latency(label: String, samples: List(Int)) {
let sorted = list.sort(samples, int.compare)
let count = list.length(sorted)
let total = list.fold(samples, 0, fn(sum, sample) { sum + sample })
let assert Ok(p50) =
list.drop(sorted, percentile_index(count, 50)) |> list.first()
let assert Ok(p95) =
list.drop(sorted, percentile_index(count, 95)) |> list.first()
io.println(label <> "_total_ms=" <> int.to_string(total))
io.println(label <> "_p50_ms=" <> int.to_string(p50))
io.println(label <> "_p95_ms=" <> int.to_string(p95))
}
fn nanoseconds_to_milliseconds(value: Int) -> Int {
value / 1_000_000
}
fn percentile_index(count: Int, percentile: Int) -> Int {
let percentage = count * percentile / 100
int.max(0, percentage - 1)
}
@external(erlang, "erlang", "system_time")
fn now() -> Int