Skip to content

Commit 04547d8

Browse files
authored
Merge pull request #14 from linksplatform/issue-13-23f296f3c251
Apply best practices from Comparisons.Neo4jVSDoublets
2 parents 1831e18 + e99c5cd commit 04547d8

File tree

13 files changed

+261
-131
lines changed

13 files changed

+261
-131
lines changed

.github/workflows/rust.yml

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
name: Benchmark Rust version
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- master
8+
pull_request:
9+
branches:
10+
- main
11+
- master
412

513
env:
614
toolchain: nightly-2022-08-22
@@ -27,41 +35,98 @@ jobs:
2735

2836
steps:
2937
- name: Checkout repository
30-
uses: actions/checkout@v2
38+
uses: actions/checkout@v4
39+
3140
- name: Setup Rust
32-
uses: actions-rs/toolchain@v1
41+
uses: dtolnay/rust-toolchain@master
3342
with:
34-
profile: minimal
3543
toolchain: ${{env.toolchain}}
3644
components: rustfmt, clippy
37-
default: true
45+
46+
- name: Wait for PostgreSQL to be ready
47+
run: |
48+
echo "Waiting for PostgreSQL to be fully ready..."
49+
for i in {1..30}; do
50+
if pg_isready -h localhost -p 5432 -U postgres > /dev/null 2>&1; then
51+
echo "PostgreSQL is ready!"
52+
break
53+
fi
54+
echo "Attempt $i: PostgreSQL not ready yet..."
55+
sleep 2
56+
done
57+
3858
- name: Build benchmark
39-
uses: actions-rs/cargo@v1
40-
with:
41-
command: build
42-
args: --release --all-features --manifest-path rust/Cargo.toml
59+
run: cargo build --release --all-features --manifest-path rust/Cargo.toml
60+
4361
- name: Run benchmark
4462
working-directory: rust
45-
run: cargo bench --bench bench -- --output-format bencher | tee out.txt
63+
env:
64+
POSTGRES_HOST: localhost
65+
POSTGRES_PORT: 5432
66+
POSTGRES_USER: postgres
67+
POSTGRES_PASSWORD: postgres
68+
POSTGRES_DB: postgres
69+
# Use 1000 links for main/master branch benchmarks, 10 for pull requests
70+
BENCHMARK_LINKS: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) && '1000' || '10' }}
71+
# Use 100000 background links for main/master branch, 100 for pull requests
72+
BENCHMARK_BACKGROUND_LINKS: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) && '100000' || '100' }}
73+
run: |
74+
set -o pipefail
75+
cargo bench --bench bench -- --output-format bencher | tee out.txt
76+
4677
- name: Prepare benchmark results
78+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
4779
run: |
4880
git config --global user.email "[email protected]"
4981
git config --global user.name "LinksPlatformBencher"
5082
cd rust
5183
pip install numpy matplotlib
5284
python3 out.py
5385
cd ..
54-
git fetch
55-
git checkout gh-pages
56-
mv -f rust/bench_rust.png Docs
57-
mv -f rust/bench_rust_log_scale.png Docs
58-
git add Docs
59-
git commit -m "Publish new benchmark results"
60-
git push origin gh-pages
86+
87+
# Create Docs directory if it doesn't exist
88+
mkdir -p Docs
89+
90+
# Copy generated images
91+
cp -f rust/bench_rust.png Docs/
92+
cp -f rust/bench_rust_log_scale.png Docs/
93+
94+
# Update README with latest results
95+
if [ -f rust/results.md ]; then
96+
# Replace the results section in README.md
97+
python3 -c "
98+
import re
99+
100+
with open('rust/results.md', 'r') as f:
101+
results = f.read()
102+
103+
with open('README.md', 'r') as f:
104+
readme = f.read()
105+
106+
# Pattern to find and replace the results table
107+
pattern = r'(\| Operation.*?\n\|[-|]+\n(?:\|.*?\n)*)'
108+
if re.search(pattern, readme):
109+
readme = re.sub(pattern, results.strip() + '\n', readme)
110+
111+
with open('README.md', 'w') as f:
112+
f.write(readme)
113+
"
114+
fi
115+
116+
# Commit changes if any
117+
git add Docs README.md
118+
if git diff --staged --quiet; then
119+
echo "No changes to commit"
120+
else
121+
git commit -m "Update benchmark results"
122+
git push origin HEAD
123+
fi
124+
61125
- name: Save benchmark results
62126
uses: actions/upload-artifact@v4
63127
with:
64128
name: Benchmark results
65129
path: |
66130
rust/bench_rust.png
67131
rust/bench_rust_log_scale.png
132+
rust/out.txt

Docs/.gitkeep

Whitespace-only changes.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ The results below represent the amount of time (ns) the operation takes per iter
2525
- Second picture shows time in a logarithmic scale (to see diffrence clearly, because it is around 2-3 orders of magnitude).
2626

2727
### Rust
28-
![Image of Rust benchmark (pixel scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/gh-pages/Docs/bench_rust.png?raw=true)
29-
![Image of Rust benchmark (log scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/gh-pages/Docs/bench_rust_log_scale.png?raw=true)
28+
![Image of Rust benchmark (pixel scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/main/Docs/bench_rust.png?raw=true)
29+
![Image of Rust benchmark (log scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/main/Docs/bench_rust_log_scale.png?raw=true)
3030

3131
### Raw benchmark results (all numbers are in nanoseconds)
3232

cpp/out.py

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import re
2+
import logging
23
import matplotlib.pyplot as plt
34
import numpy as np
45

6+
# Enable detailed tracing. Set to False to disable verbose output.
7+
DEBUG = True
8+
logging.basicConfig(level=logging.INFO if DEBUG else logging.WARNING,
9+
format="%(message)s")
10+
511
data = open('out.txt').read()
12+
if DEBUG:
13+
logging.info("Loaded out.txt, length: %d characters", len(data))
614

715
patterns = [
816
r"BM_(PSQL)/\w+\/?\w+?/(\w+)/\d+/min_warmup_time:20\.000\s+(\d+)\sns\s+\d+\sns\s+\d+",
@@ -18,15 +26,21 @@
1826

1927
for pattern in patterns:
2028
matches = re.findall(pattern, data)
29+
if DEBUG:
30+
logging.info("Pattern %s matched %d entries", pattern, len(matches))
2131
for match in matches:
2232
if match[0] == 'PSQL':
2333
_category, transaction, time = match
34+
if DEBUG:
35+
logging.info("PSQL - %s: %s ns", transaction, time)
2436
if transaction == "Transaction":
2537
PSQL_Transaction.append(int(time))
2638
else:
2739
PSQL_NonTransaction.append(int(time))
2840
else:
2941
_category, trees, storage, time = match
42+
if DEBUG:
43+
logging.info("Doublets %s %s: %s ns", trees, storage, time)
3044
if trees == 'United':
3145
if storage == 'Volatile':
3246
Doublets_United_Volatile.append(int(time))
@@ -38,69 +52,92 @@
3852
else:
3953
Doublets_Split_NonVolatile.append(int(time))
4054

55+
if DEBUG:
56+
logging.info("\nFinal lists (after parsing):")
57+
logging.info("PSQL_Transaction: %s", PSQL_Transaction)
58+
logging.info("PSQL_NonTransaction: %s", PSQL_NonTransaction)
59+
logging.info("Doublets_United_Volatile: %s", Doublets_United_Volatile)
60+
logging.info("Doublets_United_NonVolatile: %s", Doublets_United_NonVolatile)
61+
logging.info("Doublets_Split_Volatile: %s", Doublets_Split_Volatile)
62+
logging.info("Doublets_Split_NonVolatile: %s", Doublets_Split_NonVolatile)
63+
4164
labels = ['Create', 'Delete', 'Each Identity', 'Each Concrete', 'Each Outgoing', 'Each Incoming', 'Each All', 'Update']
65+
66+
# ─────────────────────────────────────────────────────────────────────────────
67+
# Plots
68+
# ─────────────────────────────────────────────────────────────────────────────
69+
def ensure_min_visible(arr, min_val):
70+
"""Ensure non-zero values are at least min_val for visibility on graph."""
71+
return [max(v, min_val) if v > 0 else 0 for v in arr]
72+
4273
def bench1():
43-
Doublets_United_Volatile_Pixels = [max(1, x // 10000000) for x in Doublets_United_Volatile]
44-
Doublets_United_NonVolatile_Pixels = [max(1, x // 10000000) for x in Doublets_United_NonVolatile]
45-
Doublets_Split_Volatile_Pixels = [max(1, x // 10000000) for x in Doublets_Split_Volatile]
46-
Doublets_Split_NonVolatile_Pixels = [max(1, x // 10000000) for x in Doublets_Split_NonVolatile]
47-
PSQL_NonTransaction_Pixels = [max(1, x // 10000000) for x in PSQL_NonTransaction]
48-
PSQL_Transaction_Pixels = [max(1, x // 10000000) for x in PSQL_Transaction]
49-
74+
"""Horizontal bars – raw values (pixel scale)."""
5075
y = np.arange(len(labels))
51-
5276
width = 0.1
53-
5477
fig, ax = plt.subplots(figsize=(12, 8))
5578

56-
57-
rects1 = ax.barh(y - 2*width, Doublets_United_Volatile_Pixels, width, label='Doublets United Volatile', color='salmon')
58-
rects2 = ax.barh(y - width, Doublets_United_NonVolatile_Pixels, width, label='Doublets United NonVolatile', color='red')
59-
60-
rects3 = ax.barh(y, Doublets_Split_Volatile_Pixels, width, label='Doublets Split Volatile', color='lightgreen')
61-
rects4 = ax.barh(y + width, Doublets_Split_NonVolatile_Pixels, width, label='Doublets Split NonVolatile', color='green')
62-
63-
rects5 = ax.barh(y + 2*width, PSQL_NonTransaction_Pixels, width, label='PSQL NonTransaction', color='lightblue')
64-
rects6 = ax.barh(y + 3*width, PSQL_Transaction_Pixels, width, label='PSQL Transaction', color='blue')
65-
66-
ax.set_xlabel('Time (ns) - Scaled to Pixels')
67-
ax.set_title('Benchmark comparison for Doublets and PostgreSQL')
79+
# Calculate maximum value across all data series to determine scale
80+
all_values = (Doublets_United_Volatile + Doublets_United_NonVolatile +
81+
Doublets_Split_Volatile + Doublets_Split_NonVolatile +
82+
PSQL_NonTransaction + PSQL_Transaction)
83+
max_val = max(all_values) if all_values else 1
84+
85+
# Minimum visible bar width: ~0.5% of max value ensures at least 2 pixels
86+
# on typical 12-inch wide figure at 100 DPI (~900px plot area)
87+
min_visible = max_val * 0.005
88+
if DEBUG:
89+
logging.info("bench1: max_val=%d, min_visible=%d", max_val, min_visible)
90+
91+
# Apply minimum visibility to all data series
92+
du_volatile_vis = ensure_min_visible(Doublets_United_Volatile, min_visible)
93+
du_nonvolatile_vis = ensure_min_visible(Doublets_United_NonVolatile, min_visible)
94+
ds_volatile_vis = ensure_min_visible(Doublets_Split_Volatile, min_visible)
95+
ds_nonvolatile_vis = ensure_min_visible(Doublets_Split_NonVolatile, min_visible)
96+
psql_non_vis = ensure_min_visible(PSQL_NonTransaction, min_visible)
97+
psql_trans_vis = ensure_min_visible(PSQL_Transaction, min_visible)
98+
99+
ax.barh(y - 2*width, du_volatile_vis, width, label='Doublets United Volatile', color='salmon')
100+
ax.barh(y - width, du_nonvolatile_vis, width, label='Doublets United NonVolatile', color='red')
101+
ax.barh(y, ds_volatile_vis, width, label='Doublets Split Volatile', color='lightgreen')
102+
ax.barh(y + width, ds_nonvolatile_vis, width, label='Doublets Split NonVolatile', color='green')
103+
ax.barh(y + 2*width, psql_non_vis, width, label='PSQL NonTransaction', color='lightblue')
104+
ax.barh(y + 3*width, psql_trans_vis, width, label='PSQL Transaction', color='blue')
105+
106+
ax.set_xlabel('Time (ns)')
107+
ax.set_title('Benchmark Comparison: PostgreSQL vs Doublets (C++)')
68108
ax.set_yticks(y)
69109
ax.set_yticklabels(labels)
70110
ax.legend()
71111

72112
fig.tight_layout()
73-
74-
plt.savefig("Bench1.png")
113+
plt.savefig("bench_cpp.png")
75114
plt.close(fig)
115+
if DEBUG: logging.info("bench_cpp.png saved.")
76116

77117
def bench2():
118+
"""Horizontal bars – raw values on a log scale."""
78119
y = np.arange(len(labels))
79-
80120
width = 0.1
81121
fig, ax = plt.subplots(figsize=(12, 8))
82122

83-
rects1 = ax.barh(y - 2*width, Doublets_United_Volatile, width, label='Doublets United Volatile', color='salmon')
84-
rects2 = ax.barh(y - width, Doublets_United_NonVolatile, width, label='Doublets United NonVolatile', color='red')
123+
ax.barh(y - 2*width, Doublets_United_Volatile, width, label='Doublets United Volatile', color='salmon')
124+
ax.barh(y - width, Doublets_United_NonVolatile, width, label='Doublets United NonVolatile', color='red')
125+
ax.barh(y, Doublets_Split_Volatile, width, label='Doublets Split Volatile', color='lightgreen')
126+
ax.barh(y + width, Doublets_Split_NonVolatile, width, label='Doublets Split NonVolatile', color='green')
127+
ax.barh(y + 2*width, PSQL_NonTransaction, width, label='PSQL NonTransaction', color='lightblue')
128+
ax.barh(y + 3*width, PSQL_Transaction, width, label='PSQL Transaction', color='blue')
85129

86-
rects3 = ax.barh(y, Doublets_Split_Volatile, width, label='Doublets Split Volatile', color='lightgreen')
87-
rects4 = ax.barh(y + width, Doublets_Split_NonVolatile, width, label='Doublets Split NonVolatile', color='green')
88-
89-
rects5 = ax.barh(y + 2*width, PSQL_NonTransaction, width, label='PSQL NonTransaction', color='lightblue')
90-
rects6 = ax.barh(y + 3*width, PSQL_Transaction, width, label='PSQL Transaction', color='blue')
91-
92-
ax.set_xlabel('Time (ns) - Logarithmic Scale')
93-
ax.set_title('Benchmark comparison for Doublets and PostgreSQL')
130+
ax.set_xlabel('Time (ns) – log scale')
131+
ax.set_title('Benchmark Comparison: PostgreSQL vs Doublets (C++)')
94132
ax.set_yticks(y)
95133
ax.set_yticklabels(labels)
96134
ax.legend()
97-
98135
ax.set_xscale('log')
99136

100137
fig.tight_layout()
101-
102-
plt.savefig("Bench2.png")
138+
plt.savefig("bench_cpp_log_scale.png")
103139
plt.close(fig)
140+
if DEBUG: logging.info("bench_cpp_log_scale.png saved.")
104141

105142
bench1()
106143
bench2()

rust/benches/benchmarks/create.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use {
88
parts::LinkPart,
99
split::{self, DataPart, IndexPart}, unit,
1010
},
11-
linkspsql::{bench, Benched, Client, connect, Exclusive, Fork, Transaction},
11+
linkspsql::{bench, benchmark_links, Benched, Client, connect, Exclusive, Fork, Transaction},
1212
std::{alloc::Global, time::{Duration, Instant}},
1313
};
1414

@@ -17,9 +17,10 @@ fn bench<T: LinkType, B: Benched + Doublets<T>>(
1717
id: &str,
1818
mut benched: B,
1919
) {
20+
let links = benchmark_links();
2021
group.bench_function(id, |bencher| {
2122
bench!(|fork| as B {
22-
for _ in 0..1_000 {
23+
for _ in 0..links {
2324
let _ = elapsed! {fork.create_point()?};
2425
}
2526
})(bencher, &mut benched);

rust/benches/benchmarks/delete.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,22 @@ use {
77
parts::LinkPart,
88
split::{self, DataPart, IndexPart}, unit,
99
},
10-
linkspsql::{bench, Benched, Client, connect, Exclusive, Fork, Transaction, BACKGROUND_LINKS},
10+
linkspsql::{bench, background_links, benchmark_links, Benched, Client, connect, Exclusive, Fork, Transaction},
1111
std::{alloc::Global, time::{Duration, Instant}},
1212
};
1313
fn bench<B: Benched + Doublets<usize>>(
1414
group: &mut BenchmarkGroup<WallTime>,
1515
id: &str,
1616
mut benched: B,
1717
) {
18+
let bg_links = background_links();
19+
let links = benchmark_links();
1820
group.bench_function(id, |bencher| {
1921
bench!(|fork| as B {
20-
for _prepare in BACKGROUND_LINKS..BACKGROUND_LINKS + 1_000 {
22+
for _prepare in bg_links..bg_links + links {
2123
let _ = fork.create_point();
2224
}
23-
for id in (BACKGROUND_LINKS..=BACKGROUND_LINKS + 1_000).rev() {
25+
for id in (bg_links..=bg_links + links).rev() {
2426
let _ = elapsed! {fork.delete(id)?};
2527
}
2628
})(bencher, &mut benched);

rust/benches/benchmarks/each/concrete.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use {
88
split::{self, DataPart, IndexPart},
99
unit, Doublets,
1010
},
11-
linkspsql::{bench, connect, Benched, Client, Exclusive, Fork, Transaction},
11+
linkspsql::{background_links, bench, connect, Benched, Client, Exclusive, Fork, Transaction},
1212
std::{
1313
alloc::Global,
1414
time::{Duration, Instant},
@@ -21,15 +21,10 @@ fn bench<B: Benched + Doublets<usize>>(
2121
) {
2222
let handler = |_| Flow::Continue;
2323
let any = LinksConstants::new().any;
24+
let bg_links = background_links();
2425
group.bench_function(id, |bencher| {
2526
bench!(|fork| as B {
26-
for index in 1..=1_000 {
27-
elapsed! {fork.each_by([any, index, index], handler)};
28-
}
29-
for index in 1_001..=2_000 {
30-
elapsed! {fork.each_by([any, index, index], handler)};
31-
}
32-
for index in 2_001..=BACKGROUND_LINKS {
27+
for index in 1..=bg_links {
3328
elapsed! {fork.each_by([any, index, index], handler)};
3429
}
3530
})(bencher, &mut benched);

0 commit comments

Comments
 (0)