Skip to content

Commit 995f652

Browse files
committed
add benchmark files
1 parent 2732ac6 commit 995f652

6 files changed

Lines changed: 403 additions & 1 deletion

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ build/
3636
compile_commands.json
3737
build_config.rb.lock
3838
.cache
39-
mruby
39+
mruby
40+
benchmark/bench_c_lmdb
41+
node_modules/
42+
package.json
43+
package-lock.json

benchmark/bench_c_lmdb.c

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <time.h>
5+
#include <sys/stat.h>
6+
#include <lmdb.h>
7+
8+
#define N 10000
9+
#define VAL_SIZE 100
10+
#define MAP_SIZE (256UL * 1024 * 1024)
11+
12+
static double now_ms(void) {
13+
struct timespec ts;
14+
clock_gettime(CLOCK_MONOTONIC, &ts);
15+
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1.0e6;
16+
}
17+
18+
static void check(int rc, const char *msg) {
19+
if (rc != 0) { fprintf(stderr, "%s: %s\n", msg, mdb_strerror(rc)); exit(1); }
20+
}
21+
22+
int main(void) {
23+
MDB_env *env; MDB_dbi dbi; MDB_txn *txn; MDB_cursor *cursor;
24+
MDB_val key, data;
25+
char keybuf[32], valbuf[VAL_SIZE];
26+
double t0, t1;
27+
int rc;
28+
29+
memset(valbuf, 'x', VAL_SIZE);
30+
31+
const char *path = "/tmp/bench_c_lmdb";
32+
mkdir(path, 0755);
33+
check(mdb_env_create(&env), "env_create");
34+
check(mdb_env_set_mapsize(env, MAP_SIZE), "set_mapsize");
35+
check(mdb_env_open(env, path, MDB_NOSYNC | MDB_NOMETASYNC, 0644), "env_open");
36+
check(mdb_txn_begin(env, NULL, 0, &txn), "txn_begin");
37+
check(mdb_dbi_open(txn, NULL, 0, &dbi), "dbi_open");
38+
check(mdb_txn_commit(txn), "txn_commit");
39+
40+
printf("=== C LMDB Benchmark (%d records, %d-byte values) ===\n\n", N, VAL_SIZE);
41+
42+
/* 1. N writes, 1 txn each */
43+
t0 = now_ms();
44+
for (int i = 0; i < N; i++) {
45+
int len = snprintf(keybuf, sizeof(keybuf), "key:%08d", i);
46+
key.mv_data = keybuf; key.mv_size = len;
47+
data.mv_data = valbuf; data.mv_size = VAL_SIZE;
48+
check(mdb_txn_begin(env, NULL, 0, &txn), "txn_begin");
49+
check(mdb_put(txn, dbi, &key, &data, 0), "put");
50+
check(mdb_txn_commit(txn), "txn_commit");
51+
}
52+
t1 = now_ms();
53+
printf("1. Write (1 txn each): %8.1f ms (%7.0f ops/s)\n",
54+
t1-t0, N/((t1-t0)/1000.0));
55+
56+
/* 2. N writes, 1 txn total */
57+
check(mdb_txn_begin(env, NULL, 0, &txn), "txn_begin");
58+
check(mdb_drop(txn, dbi, 0), "drop");
59+
check(mdb_txn_commit(txn), "txn_commit");
60+
61+
t0 = now_ms();
62+
check(mdb_txn_begin(env, NULL, 0, &txn), "txn_begin");
63+
for (int i = 0; i < N; i++) {
64+
int len = snprintf(keybuf, sizeof(keybuf), "key:%08d", i);
65+
key.mv_data = keybuf; key.mv_size = len;
66+
data.mv_data = valbuf; data.mv_size = VAL_SIZE;
67+
check(mdb_put(txn, dbi, &key, &data, 0), "put");
68+
}
69+
check(mdb_txn_commit(txn), "txn_commit");
70+
t1 = now_ms();
71+
printf("2. Write (1 txn total): %8.1f ms (%7.0f ops/s)\n",
72+
t1-t0, N/((t1-t0)/1000.0));
73+
74+
/* 3. N reads, 1 txn each */
75+
t0 = now_ms();
76+
for (int i = 0; i < N; i++) {
77+
int len = snprintf(keybuf, sizeof(keybuf), "key:%08d", i);
78+
key.mv_data = keybuf; key.mv_size = len;
79+
check(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn), "txn_begin");
80+
mdb_get(txn, dbi, &key, &data);
81+
mdb_txn_abort(txn);
82+
}
83+
t1 = now_ms();
84+
printf("3. Read (1 txn each): %8.1f ms (%7.0f ops/s)\n",
85+
t1-t0, N/((t1-t0)/1000.0));
86+
87+
/* 4. N reads, 1 txn total (cursor scan) */
88+
int count = 0;
89+
t0 = now_ms();
90+
check(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn), "txn_begin");
91+
check(mdb_cursor_open(txn, dbi, &cursor), "cursor_open");
92+
while (mdb_cursor_get(cursor, &key, &data, count == 0 ? MDB_FIRST : MDB_NEXT) == 0)
93+
count++;
94+
mdb_cursor_close(cursor);
95+
mdb_txn_abort(txn);
96+
t1 = now_ms();
97+
printf("4. Read (1 txn total): %8.1f ms (%7.0f ops/s)\n",
98+
t1-t0, count/((t1-t0)/1000.0));
99+
100+
/* 5. Prefix scan */
101+
check(mdb_txn_begin(env, NULL, 0, &txn), "txn_begin");
102+
for (int i = 0; i < N; i++) {
103+
int len = snprintf(keybuf, sizeof(keybuf), "user:%06d", i);
104+
key.mv_data = keybuf; key.mv_size = len;
105+
data.mv_data = valbuf; data.mv_size = VAL_SIZE;
106+
mdb_put(txn, dbi, &key, &data, 0);
107+
}
108+
check(mdb_txn_commit(txn), "txn_commit");
109+
110+
const char *prefix = "user:";
111+
size_t plen = strlen(prefix);
112+
count = 0;
113+
t0 = now_ms();
114+
check(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn), "txn_begin");
115+
check(mdb_cursor_open(txn, dbi, &cursor), "cursor_open");
116+
key.mv_data = (void*)prefix; key.mv_size = plen;
117+
rc = mdb_cursor_get(cursor, &key, &data, MDB_SET_RANGE);
118+
while (rc == 0) {
119+
if (key.mv_size < plen || memcmp(key.mv_data, prefix, plen) != 0) break;
120+
count++;
121+
rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
122+
}
123+
mdb_cursor_close(cursor);
124+
mdb_txn_abort(txn);
125+
t1 = now_ms();
126+
printf("5. Prefix scan (%d): %8.1f ms (%7.0f ops/s)\n",
127+
count, t1-t0, count/((t1-t0)/1000.0));
128+
129+
mdb_env_close(env);
130+
system("rm -rf /tmp/bench_c_lmdb");
131+
return 0;
132+
}

benchmark/bench_mruby_lmdb.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
N = 10_000
2+
VAL_SIZE = 100
3+
MAPSIZE = 256 * 1024 * 1024
4+
5+
def now_ms
6+
Time.now.to_f * 1000.0
7+
end
8+
9+
def fmt(n, ms)
10+
ops = (n / (ms / 1000.0)).to_i
11+
"#{ms.to_i.to_s.rjust(8)} ms (#{ops.to_s.rjust(7)} ops/s)"
12+
end
13+
14+
path = "/tmp/bench-mruby-lmdb-#{$$}"
15+
value = "x" * VAL_SIZE
16+
17+
env = MDB::Env.new(mapsize: MAPSIZE)
18+
env.open(path, MDB::NOSUBDIR | MDB::NOSYNC | MDB::NOMETASYNC)
19+
db = env.database
20+
21+
puts "=== mruby LMDB Benchmark (#{N} records, #{VAL_SIZE}-byte values) ==="
22+
puts
23+
24+
# 1. N writes, 1 txn each
25+
t0 = now_ms
26+
N.times { |i| db["key:%08d" % i] = value }
27+
t1 = now_ms
28+
puts "1. Write (1 txn each): #{fmt(N, t1 - t0)}"
29+
30+
# 2. N writes, 1 txn total
31+
db.drop
32+
t0 = now_ms
33+
env.transaction do |txn|
34+
dbi = db.dbi
35+
N.times { |i| MDB.put(txn, dbi, "key:%08d" % i, value) }
36+
end
37+
t1 = now_ms
38+
puts "2. Write (1 txn total): #{fmt(N, t1 - t0)}"
39+
40+
# 3. N reads, 1 txn each
41+
t0 = now_ms
42+
N.times { |i| db["key:%08d" % i] }
43+
t1 = now_ms
44+
puts "3. Read (1 txn each): #{fmt(N, t1 - t0)}"
45+
46+
# 4. N reads, 1 txn total (cursor scan)
47+
count = 0
48+
t0 = now_ms
49+
db.each { count += 1 }
50+
t1 = now_ms
51+
puts "4. Read (1 txn total): #{fmt(count, t1 - t0)}"
52+
53+
# 5. Prefix scan
54+
env.transaction do |txn|
55+
dbi = db.dbi
56+
N.times { |i| MDB.put(txn, dbi, "user:%06d" % i, value) }
57+
end
58+
count = 0
59+
t0 = now_ms
60+
db.each_prefix("user:") { count += 1 }
61+
t1 = now_ms
62+
puts "5. Prefix scan (#{count}): #{fmt(count, t1 - t0)}"
63+
64+
env.close
65+
File.delete(path) rescue nil
66+
File.delete("#{path}-lock") rescue nil

benchmark/bench_node_lmdb.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const { open } = require('lmdb');
2+
const fs = require('fs');
3+
4+
const N = 10_000;
5+
const VAL_SIZE = 100;
6+
7+
function nowMs() {
8+
const [s, ns] = process.hrtime();
9+
return s * 1000 + ns / 1e6;
10+
}
11+
12+
function fmt(n, ms) {
13+
return `${ms.toFixed(1).padStart(8)} ms (${Math.round(n/(ms/1000)).toString().padStart(7)} ops/s)`;
14+
}
15+
16+
async function main() {
17+
const value = 'x'.repeat(VAL_SIZE);
18+
const path = '/tmp/bench_node_lmdb';
19+
20+
if (fs.existsSync(path)) fs.rmSync(path, { recursive: true });
21+
fs.mkdirSync(path, { recursive: true });
22+
23+
const db = open({ path, mapSize: 256*1024*1024, noSync: true, noMetaSync: true, encoding: 'binary' });
24+
25+
console.log(`=== Node.js LMDB Benchmark (${N} records, ${VAL_SIZE}-byte values) ===\n`);
26+
27+
// 1. N writes, 1 txn each
28+
let t0 = nowMs();
29+
for (let i = 0; i < N; i++)
30+
await db.transaction(() => { db.put(`key:${String(i).padStart(8,'0')}`, value); });
31+
let t1 = nowMs();
32+
console.log(`1. Write (1 txn each): ${fmt(N, t1-t0)}`);
33+
34+
// 2. N writes, 1 txn total
35+
await db.clearAsync();
36+
t0 = nowMs();
37+
await db.transaction(() => {
38+
for (let i = 0; i < N; i++)
39+
db.put(`key:${String(i).padStart(8,'0')}`, value);
40+
});
41+
t1 = nowMs();
42+
console.log(`2. Write (1 txn total): ${fmt(N, t1-t0)}`);
43+
44+
// 3. N reads, 1 txn each
45+
t0 = nowMs();
46+
for (let i = 0; i < N; i++)
47+
db.get(`key:${String(i).padStart(8,'0')}`);
48+
t1 = nowMs();
49+
console.log(`3. Read (1 txn each): ${fmt(N, t1-t0)}`);
50+
51+
// 4. N reads, 1 txn total (cursor scan)
52+
let count = 0;
53+
t0 = nowMs();
54+
for (const _ of db.getRange())
55+
count++;
56+
t1 = nowMs();
57+
console.log(`4. Read (1 txn total): ${fmt(count, t1-t0)}`);
58+
59+
// 5. Prefix scan
60+
await db.transaction(() => {
61+
for (let i = 0; i < N; i++)
62+
db.put(`user:${String(i).padStart(6,'0')}`, value);
63+
});
64+
count = 0;
65+
t0 = nowMs();
66+
for (const { key } of db.getRange({ start: 'user:', end: 'user;' }))
67+
count++;
68+
t1 = nowMs();
69+
console.log(`5. Prefix scan (${count}): ${fmt(count, t1-t0)}`);
70+
71+
await db.close();
72+
fs.rmSync(path, { recursive: true, force: true });
73+
}
74+
75+
main().catch(console.error);

benchmark/bench_python_lmdb.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import os, time, shutil, lmdb
2+
3+
N = 10_000
4+
VAL_SIZE = 100
5+
MAP_SIZE = 256 * 1024 * 1024
6+
7+
def now_ms():
8+
return time.monotonic() * 1000.0
9+
10+
def fmt(n, ms):
11+
return f"{ms:8.1f} ms ({n/(ms/1000):7.0f} ops/s)"
12+
13+
path = "/tmp/bench_python_lmdb"
14+
value = b'x' * VAL_SIZE
15+
16+
if os.path.exists(path): shutil.rmtree(path)
17+
os.makedirs(path)
18+
env = lmdb.open(path, map_size=MAP_SIZE, sync=False, metasync=False)
19+
20+
print(f"=== Python LMDB Benchmark ({N} records, {VAL_SIZE}-byte values) ===\n")
21+
22+
# 1. N writes, 1 txn each
23+
t0 = now_ms()
24+
for i in range(N):
25+
with env.begin(write=True) as txn:
26+
txn.put(f"key:{i:08d}".encode(), value)
27+
t1 = now_ms()
28+
print(f"1. Write (1 txn each): {fmt(N, t1-t0)}")
29+
30+
# 2. N writes, 1 txn total
31+
with env.begin(write=True) as txn:
32+
txn.drop(env.open_db(), delete=False)
33+
t0 = now_ms()
34+
with env.begin(write=True) as txn:
35+
for i in range(N):
36+
txn.put(f"key:{i:08d}".encode(), value)
37+
t1 = now_ms()
38+
print(f"2. Write (1 txn total): {fmt(N, t1-t0)}")
39+
40+
# 3. N reads, 1 txn each
41+
t0 = now_ms()
42+
for i in range(N):
43+
with env.begin() as txn:
44+
txn.get(f"key:{i:08d}".encode())
45+
t1 = now_ms()
46+
print(f"3. Read (1 txn each): {fmt(N, t1-t0)}")
47+
48+
# 4. N reads, 1 txn total (cursor scan)
49+
count = 0
50+
t0 = now_ms()
51+
with env.begin() as txn:
52+
cursor = txn.cursor()
53+
for _ in cursor.iternext():
54+
count += 1
55+
t1 = now_ms()
56+
print(f"4. Read (1 txn total): {fmt(count, t1-t0)}")
57+
58+
# 5. Prefix scan
59+
with env.begin(write=True) as txn:
60+
for i in range(N):
61+
txn.put(f"user:{i:06d}".encode(), value)
62+
prefix = b"user:"
63+
count = 0
64+
t0 = now_ms()
65+
with env.begin() as txn:
66+
cursor = txn.cursor()
67+
cursor.set_range(prefix)
68+
for k, _ in cursor:
69+
if not k.startswith(prefix): break
70+
count += 1
71+
t1 = now_ms()
72+
print(f"5. Prefix scan ({count}): {fmt(count, t1-t0)}")
73+
74+
env.close()
75+
shutil.rmtree(path, ignore_errors=True)

0 commit comments

Comments
 (0)