summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-09-14 00:06:33 -0400
committerJohn Turner <jturner.usa@gmail.com>2025-09-14 00:06:33 -0400
commitc6bd9c80ef5038cca580191148d9b88ab505bcf6 (patch)
tree596ab05ee81a5d5ad5c351fdef5e78eb50efe708
downloadsqlite-kv-bench-c6bd9c80ef5038cca580191148d9b88ab505bcf6.tar.gz
init
-rw-r--r--.dir-locals.el19
-rw-r--r--README.org12
-rw-r--r--meson.build14
-rwxr-xr-xscripts/mkdb.py73
-rw-r--r--src/main.cpp46
-rw-r--r--subprojects/.wraplock0
m---------subprojects/boost-sqlite6
7 files changed, 170 insertions, 0 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..a1a3538
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,19 @@
+((c++-ts-mode
+ . ((fmt-executable . "clang-format")
+ (eval . (setq-local fmt-args `("--assume-filename" ,(buffer-file-name))))
+ (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t))
+ (eval . (add-to-list 'eglot-server-programs '(c++-mode . ("clangd" "-header-insertion=never" "-clang-tidy"))))
+ (eval . (eglot-ensure))
+ (eval . (add-hook 'eglot-managed-mode-hook (lambda ()
+ (eglot-inlay-hints-mode -1))))
+ (eval . (company-mode 1))))
+ (meson-mode
+ . ((fmt-executable . "meson")
+ (fmt-args . ("format" "-"))
+ (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t))))
+ (python-ts-mode . ((eval . (flycheck-mode 1))
+ (eval . (flycheck-select-checker 'python-mypy))
+ (eval . (flycheck-add-next-checker 'python-mypy (cons t 'python-flake8)))
+ (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t))
+ (eval . (setq-local fmt-executable "black"
+ fmt-args '("-"))))))
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..0a4609e
--- /dev/null
+++ b/README.org
@@ -0,0 +1,12 @@
+* how to use
+First create the test database with ~scripts/mkdb.py~.
+
+The benchmark executable reads keys from stdin.
+
+An example run of the benchmark:
+
+#+BEGIN_SRC_BASH
+sqlite3 "${database}" "select kv.key from kv" | shuf | bench ${database}
+#+END_SRC
+
+
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..642de1e
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,14 @@
+project(
+ 'sqlite-kv-bench',
+ 'cpp',
+ meson_version: '>=1.4.0',
+ default_options: ['warning_level=3', 'cpp_std=c++23'],
+)
+
+cmake = import('cmake')
+
+boost_sqlite = cmake.subproject('boost-sqlite').dependency('boost-sqlite')
+
+sources = files('src/main.cpp')
+
+executable('bench', sources, dependencies: [boost_sqlite])
diff --git a/scripts/mkdb.py b/scripts/mkdb.py
new file mode 100755
index 0000000..d3786f1
--- /dev/null
+++ b/scripts/mkdb.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+import sys
+import sqlite3
+import string
+from argparse import ArgumentParser
+from pathlib import Path
+from random import choice, randrange
+
+characters = string.ascii_letters + string.digits
+
+
+class Range:
+
+ def __init__(self, arg: str):
+ start, end = arg.split("-")
+
+ self.start = int(start)
+ self.end = int(end)
+
+
+def truncate(path: Path) -> None:
+ with open(path, "w") as file:
+ file.truncate(0)
+
+
+def main() -> int:
+ parser = ArgumentParser()
+
+ parser.add_argument("--database", type=Path, required=True)
+ parser.add_argument("--word-list", type=Path, required=True)
+ parser.add_argument("--num-records", type=int, required=True)
+ parser.add_argument("--key-length", type=Range, required=True)
+ parser.add_argument("--value-length", type=Range, required=True)
+
+ args = parser.parse_args()
+
+ words = args.word_list.read_text().split("\n")
+
+ truncate(args.database)
+
+ def generate_key(length: int) -> str:
+ return "".join(choice(characters) for _ in range(length))
+
+ def generate_value(length: int) -> str:
+ return "-".join(choice(words) for _ in range(length))
+
+ with sqlite3.connect(args.database) as connection:
+ connection.execute("PRAGMA jorunal_mode = WAL")
+ connection.execute("create table kv(key text, value text)")
+ connection.execute("create index keys on kv(key)")
+
+ for i in range(args.num_records):
+ key = generate_key(randrange(args.key_length.start, args.key_length.end))
+
+ val = generate_value(
+ randrange(args.value_length.start, args.value_length.end)
+ )
+
+ print(f"inserting record number {i}", file=sys.stderr)
+
+ connection.execute(
+ "insert into kv values(?, ?)",
+ (
+ key,
+ val,
+ ),
+ )
+
+ return 0
+
+
+sys.exit(main())
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..b75640d
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,46 @@
+#include <chrono>
+#include <iostream>
+#include <print>
+#include <string>
+#include <vector>
+
+#include <boost/sqlite.hpp>
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ std::println(stderr, "usage: bench <database>");
+ return 1;
+ }
+
+ std::println(stderr, "starting benchmark");
+
+ boost::sqlite::connection connection{argv[1]};
+
+ std::vector<std::string> keys;
+
+ std::string line;
+ while (std::getline(std::cin, line)) {
+ keys.push_back(line);
+ }
+
+ std::println(stderr, "slurped {} keys into memory", keys.size());
+
+ auto start = std::chrono::system_clock::now();
+
+ auto st = connection.prepare("select kv.value from kv where kv.key=?");
+
+ for (const auto &key : keys) {
+ auto row = st.execute({key});
+
+ auto value = row.current().at(0).get_text();
+ }
+
+ auto end = std::chrono::system_clock::now();
+
+ std::chrono::duration<double> d = end - start;
+
+ std::println(stderr, "selected {} keys in {} ({})", keys.size(), d,
+ keys.size() / d.count());
+
+ return 0;
+}
diff --git a/subprojects/.wraplock b/subprojects/.wraplock
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/subprojects/.wraplock
diff --git a/subprojects/boost-sqlite b/subprojects/boost-sqlite
new file mode 160000
+Subproject 3378e353705271e569cf4ba15c467b840a39798