Skip to content
Merged
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
99 changes: 82 additions & 17 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
name: Benchmark Rust version

on: [push, pull_request]
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master

env:
toolchain: nightly-2022-08-22
Expand All @@ -27,41 +35,98 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
profile: minimal
toolchain: ${{env.toolchain}}
components: rustfmt, clippy
default: true

- name: Wait for PostgreSQL to be ready
run: |
echo "Waiting for PostgreSQL to be fully ready..."
for i in {1..30}; do
if pg_isready -h localhost -p 5432 -U postgres > /dev/null 2>&1; then
echo "PostgreSQL is ready!"
break
fi
echo "Attempt $i: PostgreSQL not ready yet..."
sleep 2
done

- name: Build benchmark
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all-features --manifest-path rust/Cargo.toml
run: cargo build --release --all-features --manifest-path rust/Cargo.toml

- name: Run benchmark
working-directory: rust
run: cargo bench --bench bench -- --output-format bencher | tee out.txt
env:
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
# Use 1000 links for main/master branch benchmarks, 10 for pull requests
BENCHMARK_LINKS: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) && '1000' || '10' }}
# Use 100000 background links for main/master branch, 100 for pull requests
BENCHMARK_BACKGROUND_LINKS: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) && '100000' || '100' }}
run: |
set -o pipefail
cargo bench --bench bench -- --output-format bencher | tee out.txt

- name: Prepare benchmark results
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
run: |
git config --global user.email "linksplatform@gmail.com"
git config --global user.name "LinksPlatformBencher"
cd rust
pip install numpy matplotlib
python3 out.py
cd ..
git fetch
git checkout gh-pages
mv -f rust/bench_rust.png Docs
mv -f rust/bench_rust_log_scale.png Docs
git add Docs
git commit -m "Publish new benchmark results"
git push origin gh-pages

# Create Docs directory if it doesn't exist
mkdir -p Docs

# Copy generated images
cp -f rust/bench_rust.png Docs/
cp -f rust/bench_rust_log_scale.png Docs/

# Update README with latest results
if [ -f rust/results.md ]; then
# Replace the results section in README.md
python3 -c "
import re

with open('rust/results.md', 'r') as f:
results = f.read()

with open('README.md', 'r') as f:
readme = f.read()

# Pattern to find and replace the results table
pattern = r'(\| Operation.*?\n\|[-|]+\n(?:\|.*?\n)*)'
if re.search(pattern, readme):
readme = re.sub(pattern, results.strip() + '\n', readme)

with open('README.md', 'w') as f:
f.write(readme)
"
fi

# Commit changes if any
git add Docs README.md
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "Update benchmark results"
git push origin HEAD
fi

- name: Save benchmark results
uses: actions/upload-artifact@v4
with:
name: Benchmark results
path: |
rust/bench_rust.png
rust/bench_rust_log_scale.png
rust/out.txt
Empty file added Docs/.gitkeep
Empty file.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ The results below represent the amount of time (ns) the operation takes per iter
- Second picture shows time in a logarithmic scale (to see diffrence clearly, because it is around 2-3 orders of magnitude).

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

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

Expand Down
111 changes: 74 additions & 37 deletions cpp/out.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import re
import logging
import matplotlib.pyplot as plt
import numpy as np

# Enable detailed tracing. Set to False to disable verbose output.
DEBUG = True
logging.basicConfig(level=logging.INFO if DEBUG else logging.WARNING,
format="%(message)s")

data = open('out.txt').read()
if DEBUG:
logging.info("Loaded out.txt, length: %d characters", len(data))

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

for pattern in patterns:
matches = re.findall(pattern, data)
if DEBUG:
logging.info("Pattern %s matched %d entries", pattern, len(matches))
for match in matches:
if match[0] == 'PSQL':
_category, transaction, time = match
if DEBUG:
logging.info("PSQL - %s: %s ns", transaction, time)
if transaction == "Transaction":
PSQL_Transaction.append(int(time))
else:
PSQL_NonTransaction.append(int(time))
else:
_category, trees, storage, time = match
if DEBUG:
logging.info("Doublets %s %s: %s ns", trees, storage, time)
if trees == 'United':
if storage == 'Volatile':
Doublets_United_Volatile.append(int(time))
Expand All @@ -38,69 +52,92 @@
else:
Doublets_Split_NonVolatile.append(int(time))

if DEBUG:
logging.info("\nFinal lists (after parsing):")
logging.info("PSQL_Transaction: %s", PSQL_Transaction)
logging.info("PSQL_NonTransaction: %s", PSQL_NonTransaction)
logging.info("Doublets_United_Volatile: %s", Doublets_United_Volatile)
logging.info("Doublets_United_NonVolatile: %s", Doublets_United_NonVolatile)
logging.info("Doublets_Split_Volatile: %s", Doublets_Split_Volatile)
logging.info("Doublets_Split_NonVolatile: %s", Doublets_Split_NonVolatile)

labels = ['Create', 'Delete', 'Each Identity', 'Each Concrete', 'Each Outgoing', 'Each Incoming', 'Each All', 'Update']

# ─────────────────────────────────────────────────────────────────────────────
# Plots
# ─────────────────────────────────────────────────────────────────────────────
def ensure_min_visible(arr, min_val):
"""Ensure non-zero values are at least min_val for visibility on graph."""
return [max(v, min_val) if v > 0 else 0 for v in arr]

def bench1():
Doublets_United_Volatile_Pixels = [max(1, x // 10000000) for x in Doublets_United_Volatile]
Doublets_United_NonVolatile_Pixels = [max(1, x // 10000000) for x in Doublets_United_NonVolatile]
Doublets_Split_Volatile_Pixels = [max(1, x // 10000000) for x in Doublets_Split_Volatile]
Doublets_Split_NonVolatile_Pixels = [max(1, x // 10000000) for x in Doublets_Split_NonVolatile]
PSQL_NonTransaction_Pixels = [max(1, x // 10000000) for x in PSQL_NonTransaction]
PSQL_Transaction_Pixels = [max(1, x // 10000000) for x in PSQL_Transaction]

"""Horizontal bars – raw values (pixel scale)."""
y = np.arange(len(labels))

width = 0.1

fig, ax = plt.subplots(figsize=(12, 8))


rects1 = ax.barh(y - 2*width, Doublets_United_Volatile_Pixels, width, label='Doublets United Volatile', color='salmon')
rects2 = ax.barh(y - width, Doublets_United_NonVolatile_Pixels, width, label='Doublets United NonVolatile', color='red')

rects3 = ax.barh(y, Doublets_Split_Volatile_Pixels, width, label='Doublets Split Volatile', color='lightgreen')
rects4 = ax.barh(y + width, Doublets_Split_NonVolatile_Pixels, width, label='Doublets Split NonVolatile', color='green')

rects5 = ax.barh(y + 2*width, PSQL_NonTransaction_Pixels, width, label='PSQL NonTransaction', color='lightblue')
rects6 = ax.barh(y + 3*width, PSQL_Transaction_Pixels, width, label='PSQL Transaction', color='blue')

ax.set_xlabel('Time (ns) - Scaled to Pixels')
ax.set_title('Benchmark comparison for Doublets and PostgreSQL')
# Calculate maximum value across all data series to determine scale
all_values = (Doublets_United_Volatile + Doublets_United_NonVolatile +
Doublets_Split_Volatile + Doublets_Split_NonVolatile +
PSQL_NonTransaction + PSQL_Transaction)
max_val = max(all_values) if all_values else 1

# Minimum visible bar width: ~0.5% of max value ensures at least 2 pixels
# on typical 12-inch wide figure at 100 DPI (~900px plot area)
min_visible = max_val * 0.005
if DEBUG:
logging.info("bench1: max_val=%d, min_visible=%d", max_val, min_visible)

# Apply minimum visibility to all data series
du_volatile_vis = ensure_min_visible(Doublets_United_Volatile, min_visible)
du_nonvolatile_vis = ensure_min_visible(Doublets_United_NonVolatile, min_visible)
ds_volatile_vis = ensure_min_visible(Doublets_Split_Volatile, min_visible)
ds_nonvolatile_vis = ensure_min_visible(Doublets_Split_NonVolatile, min_visible)
psql_non_vis = ensure_min_visible(PSQL_NonTransaction, min_visible)
psql_trans_vis = ensure_min_visible(PSQL_Transaction, min_visible)

ax.barh(y - 2*width, du_volatile_vis, width, label='Doublets United Volatile', color='salmon')
ax.barh(y - width, du_nonvolatile_vis, width, label='Doublets United NonVolatile', color='red')
ax.barh(y, ds_volatile_vis, width, label='Doublets Split Volatile', color='lightgreen')
ax.barh(y + width, ds_nonvolatile_vis, width, label='Doublets Split NonVolatile', color='green')
ax.barh(y + 2*width, psql_non_vis, width, label='PSQL NonTransaction', color='lightblue')
ax.barh(y + 3*width, psql_trans_vis, width, label='PSQL Transaction', color='blue')

ax.set_xlabel('Time (ns)')
ax.set_title('Benchmark Comparison: PostgreSQL vs Doublets (C++)')
ax.set_yticks(y)
ax.set_yticklabels(labels)
ax.legend()

fig.tight_layout()

plt.savefig("Bench1.png")
plt.savefig("bench_cpp.png")
plt.close(fig)
if DEBUG: logging.info("bench_cpp.png saved.")

def bench2():
"""Horizontal bars – raw values on a log scale."""
y = np.arange(len(labels))

width = 0.1
fig, ax = plt.subplots(figsize=(12, 8))

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

rects3 = ax.barh(y, Doublets_Split_Volatile, width, label='Doublets Split Volatile', color='lightgreen')
rects4 = ax.barh(y + width, Doublets_Split_NonVolatile, width, label='Doublets Split NonVolatile', color='green')

rects5 = ax.barh(y + 2*width, PSQL_NonTransaction, width, label='PSQL NonTransaction', color='lightblue')
rects6 = ax.barh(y + 3*width, PSQL_Transaction, width, label='PSQL Transaction', color='blue')

ax.set_xlabel('Time (ns) - Logarithmic Scale')
ax.set_title('Benchmark comparison for Doublets and PostgreSQL')
ax.set_xlabel('Time (ns) – log scale')
ax.set_title('Benchmark Comparison: PostgreSQL vs Doublets (C++)')
ax.set_yticks(y)
ax.set_yticklabels(labels)
ax.legend()

ax.set_xscale('log')

fig.tight_layout()

plt.savefig("Bench2.png")
plt.savefig("bench_cpp_log_scale.png")
plt.close(fig)
if DEBUG: logging.info("bench_cpp_log_scale.png saved.")

bench1()
bench2()
5 changes: 3 additions & 2 deletions rust/benches/benchmarks/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use {
parts::LinkPart,
split::{self, DataPart, IndexPart}, unit,
},
linkspsql::{bench, Benched, Client, connect, Exclusive, Fork, Transaction},
linkspsql::{bench, benchmark_links, Benched, Client, connect, Exclusive, Fork, Transaction},
std::{alloc::Global, time::{Duration, Instant}},
};

Expand All @@ -17,9 +17,10 @@ fn bench<T: LinkType, B: Benched + Doublets<T>>(
id: &str,
mut benched: B,
) {
let links = benchmark_links();
group.bench_function(id, |bencher| {
bench!(|fork| as B {
for _ in 0..1_000 {
for _ in 0..links {
let _ = elapsed! {fork.create_point()?};
}
})(bencher, &mut benched);
Expand Down
8 changes: 5 additions & 3 deletions rust/benches/benchmarks/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ use {
parts::LinkPart,
split::{self, DataPart, IndexPart}, unit,
},
linkspsql::{bench, Benched, Client, connect, Exclusive, Fork, Transaction, BACKGROUND_LINKS},
linkspsql::{bench, background_links, benchmark_links, Benched, Client, connect, Exclusive, Fork, Transaction},
std::{alloc::Global, time::{Duration, Instant}},
};
fn bench<B: Benched + Doublets<usize>>(
group: &mut BenchmarkGroup<WallTime>,
id: &str,
mut benched: B,
) {
let bg_links = background_links();
let links = benchmark_links();
group.bench_function(id, |bencher| {
bench!(|fork| as B {
for _prepare in BACKGROUND_LINKS..BACKGROUND_LINKS + 1_000 {
for _prepare in bg_links..bg_links + links {
let _ = fork.create_point();
}
for id in (BACKGROUND_LINKS..=BACKGROUND_LINKS + 1_000).rev() {
for id in (bg_links..=bg_links + links).rev() {
let _ = elapsed! {fork.delete(id)?};
}
})(bencher, &mut benched);
Expand Down
11 changes: 3 additions & 8 deletions rust/benches/benchmarks/each/concrete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use {
split::{self, DataPart, IndexPart},
unit, Doublets,
},
linkspsql::{bench, connect, Benched, Client, Exclusive, Fork, Transaction},
linkspsql::{background_links, bench, connect, Benched, Client, Exclusive, Fork, Transaction},
std::{
alloc::Global,
time::{Duration, Instant},
Expand All @@ -21,15 +21,10 @@ fn bench<B: Benched + Doublets<usize>>(
) {
let handler = |_| Flow::Continue;
let any = LinksConstants::new().any;
let bg_links = background_links();
group.bench_function(id, |bencher| {
bench!(|fork| as B {
for index in 1..=1_000 {
elapsed! {fork.each_by([any, index, index], handler)};
}
for index in 1_001..=2_000 {
elapsed! {fork.each_by([any, index, index], handler)};
}
for index in 2_001..=BACKGROUND_LINKS {
for index in 1..=bg_links {
elapsed! {fork.each_by([any, index, index], handler)};
}
})(bencher, &mut benched);
Expand Down
Loading