大更新:

1、完善SQL语法
2、移除node前端,只保留静态资源
3、写好安装脚本
4、做好linux适配
This commit is contained in:
LIm1tZz7 committed 2026-10-01 20:48:38 +08:00
1 parent 687c012841
commit df6d348995
166 files changed
+16317 -10763

No files matched your search

@@ -1,14 +0,0 @@
cabal-version: 2.4
name: chusql-bench
version: 1.0.0
synopsis: ChuSQL 查询优化基准测试(对比开/关优化器)
build-type: Simple
executable chusql-bench
main-is: Main.hs
hs-source-dirs: src
build-depends: base >= 4.14 && < 5
, chusql-engine
default-language: Haskell2010
ghc-options: -O2 -Wall
-159
View File
@@ -1,159 +0,0 @@
module Main where
import ChuSQL.Algebra.Eval (evalRelOp)
import ChuSQL.Algebra.Optimize (optimize)
import ChuSQL.Algebra.Planner (translate)
import ChuSQL.Model
import ChuSQL.Syntax.Parser (parseStatement)
import Control.Exception (evaluate)
import Data.IORef (IORef, newIORef, readIORef)
import Data.List (sort)
import System.CPUTime (getCPUTime)
import System.Environment (getArgs)
import Text.Printf (printf)
-- 性能基准:同一批语句,比较优化前后的耗时。
-- | 造一张 users 表
mkUsers :: Int -> Table
mkUsers n =
Table
{ tableName = "users"
, tableCols = [("id", TInt), ("name", TStr), ("age", TInt)]
, tableRows =
[ [("id", VInt i), ("name", VStr ("user" ++ show i)), ("age", VInt (i `mod` 100))]
| i <- [1 .. n]
]
}
-- | 造一张 orders 表
mkOrders :: Int -> Int -> Table
mkOrders n m =
Table
{ tableName = "orders"
, tableCols = [("id", TInt), ("user_id", TInt), ("product", TStr)]
, tableRows =
[ [("id", VInt j), ("user_id", VInt (1 + (j - 1) `mod` n)), ("product", VStr ("item" ++ show (j `mod` 50)))]
| j <- [1 .. m]
]
}
-- | 结果校验和(防被优化掉)
checksum :: Either String [Row] -> Int
checksum (Left _) = -1
checksum (Right rows) = go 0 rows
where
-- | 累加行长
go acc [] = acc
go acc (r : rs) = go (acc + length r) rs
-- | 结果行数
rowCount :: Either String [Row] -> Int
rowCount (Left _) = -1
rowCount (Right rows) = length rows
-- | 跑一次,返回校验和
runOnce :: IORef String -> Database -> String -> Bool -> IO Int
runOnce saltRef db sql useOpt = do
salt <- readIORef saltRef
let result = case parseStatement (sql ++ salt) >>= translate of
Left e -> Left e
Right plan -> evalRelOp db (if useOpt then optimize db plan else plan)
forced <- evaluate (checksum result)
return forced
-- | 跑一次,返回行数
resultRows :: IORef String -> Database -> String -> Bool -> IO Int
resultRows saltRef db sql useOpt = do
salt <- readIORef saltRef
let result = case parseStatement (sql ++ salt) >>= translate of
Left e -> Left e
Right plan -> evalRelOp db (if useOpt then optimize db plan else plan)
_ <- evaluate (checksum result)
return (rowCount result)
-- | 量一段 IO 的 CPU 时间
timed :: IO a -> IO Double
timed act = do
t0 <- getCPUTime
_ <- act
t1 <- getCPUTime
return (fromIntegral (t1 - t0) / 1e12)
minBatch :: Int
minBatch = 8
rounds :: Int
rounds = 3
-- | 跑一批 k 次,返回总耗时
timeBatch :: Int -> IO Int -> IO Double
timeBatch k act = timed (mapM_ (const act) [1 .. k])
-- | 中位数
median :: [Double] -> Double
median xs = sort xs !! (length xs `div` 2)
-- | 自适应批量(够长且至少 8 次),取 3 批中位数
benchOp :: Double -> IO Int -> IO (Double, Int)
benchOp target act = do
k <- pickBatch 1
ts <- mapM (const (timeBatch k act)) [1 .. rounds]
return (median ts / fromIntegral k, k)
where
-- | 批次翻倍直到累计超过目标
pickBatch k = do
t <- timeBatch k act
if t < target || k < minBatch then pickBatch (k * 2) else return k
-- | 打印一个用例的对比
report :: IORef String -> Database -> Double -> String -> IO ()
report saltRef db target sql = do
nUn <- resultRows saltRef db sql False
nOp <- resultRows saltRef db sql True
(tUn, kUn) <- benchOp target (runOnce saltRef db sql False)
(tOp, kOp) <- benchOp target (runOnce saltRef db sql True)
let speedup = if tOp <= 0 then 0 else tUn / tOp
mismatch = if nUn == nOp then "" else " <<< result mismatch!"
printf "%s\n" sql
printf
" rows %-6d | baseline %9.3f ms | optimized %9.3f ms | speedup %6.1fx%s\n"
nOp
(tUn * 1000)
(tOp * 1000)
speedup
mismatch
printf
" (%d iters/batch x %d batches, median: baseline %d iters/batch, optimized %d iters/batch)\n\n"
minBatch
rounds
kUn
kOp
queries :: [String]
queries =
[ "SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 90"
, "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id"
, "SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 90 ORDER BY o.product LIMIT 10"
, "SELECT name FROM users WHERE age > 90"
]
warmupSql :: String
warmupSql = "SELECT name FROM users"
-- | 基准入口
main :: IO ()
main = do
args <- getArgs
let pick i d = if length args > i then read (args !! i) else d
n = pick 0 500
m = pick 1 500
target = pick 2 (0.3 :: Double)
db = [("users", mkUsers n), ("orders", mkOrders n m)]
printf "dataset: users = %d rows, orders = %d rows\n" n m
printf "timing: CPU time; at least %d iterations per batch, batch fixed after %.2f s, median of %d batches\n" minBatch target rounds
printf "(one warmup run first, so building the data is not counted)\n\n"
saltRef <- newIORef ""
_ <- runOnce saltRef db warmupSql False
_ <- runOnce saltRef db warmupSql True
mapM_ (report saltRef db target) queries
-5
View File
@@ -1,5 +0,0 @@
resolver: lts-24.59
packages:
- .
- ../../chusql-engine
File renamed without changes.
-334
View File
@@ -1,334 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "cfg-if"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "chusql-storage"
version = "0.1.0"
dependencies = [
"interprocess",
"serde",
"serde_json",
"toml",
]
[[package]]
name = "chusql-storage-bench"
version = "0.1.0"
dependencies = [
"chusql-storage",
"serde_json",
"tempfile",
]
[[package]]
name = "doctest-file"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "interprocess"
version = "2.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb02ffbf25e8ef0f4a95bff054eef75f6d1bf16919474cf2caa63b21997478a"
dependencies = [
"doctest-file",
"libc",
"recvmsg",
"widestring",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "recvmsg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "syn"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "unicode-ident"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "widestring"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
-16
View File
@@ -1,16 +0,0 @@
[package]
name = "chusql-storage-bench"
version = "0.1.0"
edition = "2024"
# 存储层基准单独成包,不混进功能代码(见同目录 README)。
[dependencies]
chusql-storage = { path = "../../chusql-storage" }
serde_json = "1"
[dev-dependencies]
tempfile = "3"
[[bench]]
name = "storage"
harness = false
-337
View File
@@ -1,337 +0,0 @@
use std::time::{Duration, Instant};
use chusql_storage::btree::DiskBTree;
use chusql_storage::config::{DEFAULT_BTREE_ORDER, DEFAULT_PAGE_SIZE};
use chusql_storage::heap::HeapTable;
use chusql_storage::page::{Page, PageFile};
use chusql_storage::protocol::Row;
use chusql_storage::wal::{Wal, WalOp};
use serde_json::json;
// 存储层基准:页 IO、B+ 树、堆表、WAL 与服务端写入路径;
// 用 std::time::Instant 计时,不引第三方基准框架,
// 每项跑若干轮取最快一轮,离线也能跑。
/// 跑一个操作 ops 次,量总耗时并折算每次耗时
fn bench<F: FnMut()>(label: &str, ops: usize, rounds: usize, mut f: F) {
let mut best = Duration::MAX;
for _ in 0..rounds {
let t0 = Instant::now();
f();
let d = t0.elapsed();
if d < best {
best = d;
}
}
let per = best.as_secs_f64() * 1e6 / ops as f64;
println!(
"{:<38} {:>9.2} ms {:>10.3} us/op",
label,
best.as_secs_f64() * 1e3,
per
);
}
/// 只量一次(fsync 类操作太慢,不适合反复跑)
fn bench_once<F: FnOnce()>(label: &str, ops: usize, f: F) {
let t0 = Instant::now();
f();
let d = t0.elapsed();
let per = d.as_secs_f64() * 1e6 / ops as f64;
println!(
"{:<38} {:>9.2} ms {:>10.3} us/op",
label,
d.as_secs_f64() * 1e3,
per
);
}
/// 造一行
fn row(id: i64) -> Row {
let mut m = Row::new();
m.insert("id".to_string(), json!(id));
m.insert("name".to_string(), json!(format!("user{}", id)));
m
}
/// 小随机数发生器,避免引 rand
fn next_rand(state: &mut u64) -> u64 {
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
*state >> 33
}
/// 顺序写 / 顺序读 / 随机读
fn bench_page(dir: &std::path::Path, n: usize) {
let path = dir.join("page.db");
let mut pf = PageFile::with_options(&path, DEFAULT_PAGE_SIZE, 1024).unwrap();
bench("page sequential write (with flush)", n, 3, || {
pf.truncate().unwrap();
for i in 0..n {
pf.write_page(&Page::new(i as u64, DEFAULT_PAGE_SIZE)).unwrap();
}
pf.flush().unwrap();
});
drop(pf);
let mut pf = PageFile::with_options(&path, DEFAULT_PAGE_SIZE, 16).unwrap();
bench("page sequential read (pool 16, mostly misses)", n, 3, || {
for i in 0..n {
pf.read_page(i as u64).unwrap();
}
});
let mut pf = PageFile::with_options(&path, DEFAULT_PAGE_SIZE, 16).unwrap();
let mut state = 12345u64;
bench("page random read (pool 16)", n, 3, || {
for _ in 0..n {
let id = next_rand(&mut state) % n as u64;
pf.read_page(id).unwrap();
}
});
let mut pf = PageFile::with_options(&path, DEFAULT_PAGE_SIZE, 1024).unwrap();
let mut state = 999u64;
bench("page random read (pool 1024, working set > pool)", n, 3, || {
for _ in 0..n {
let id = next_rand(&mut state) % n as u64;
pf.read_page(id).unwrap();
}
});
println!(
" (pool 1024: hits {}/{})",
pf.hits(),
pf.hits() + pf.misses()
);
}
/// 插入、点查、全量遍历
fn bench_btree(dir: &std::path::Path, n: usize) {
let path = dir.join("bt.idx");
let _ = std::fs::remove_file(&path);
let mut bt = DiskBTree::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_BTREE_ORDER, 1024).unwrap();
bench("btree insert (with flush)", n, 2, || {
bt.clear().unwrap();
for i in 0..n {
bt.insert(i as i64, i as u64).unwrap();
}
bt.flush().unwrap();
});
bench("btree point lookup (all hits)", n, 3, || {
for i in 0..n {
bt.get(i as i64).unwrap().expect("key must exist");
}
});
bench("btree full scan", n, 3, || {
let v = bt.iter_all().unwrap();
assert_eq!(v.len(), n);
});
}
/// 插行、全表扫描、按索引取行
fn bench_heap(dir: &std::path::Path, n: usize) {
let path = dir.join("heap.db");
let idx = dir.join("heap.idx");
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(&idx);
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, 1024).unwrap();
bench("heap insert (buffer pool only, no flush)", n, 2, || {
t.replace_all(&[]).unwrap();
for i in 0..n {
t.insert_row(&row(i as i64)).unwrap();
}
});
bench("heap insert + flush", n, 2, || {
t.replace_all(&[]).unwrap();
for i in 0..n {
t.insert_row(&row(i as i64)).unwrap();
}
t.flush().unwrap();
});
bench("heap batch insert + flush (single commit)", n, 2, || {
t.replace_all(&[]).unwrap();
let rows: Vec<Row> = (0..n as i64).map(row).collect();
t.insert_rows(&rows).unwrap();
t.flush().unwrap();
});
bench("heap full scan", n, 3, || {
let rows = t.scan().unwrap();
assert_eq!(rows.len(), n);
});
}
/// 追加写与清空
fn bench_wal(dir: &std::path::Path, n: usize) {
let path = dir.join("wal.log");
let _ = std::fs::remove_file(&path);
let wal = Wal::new(&path);
let op = WalOp::Insert {
table: "t".to_string(),
row: row(1),
};
bench_once("wal append (fsync per record)", n, || {
for _ in 0..n {
wal.append(&op).unwrap();
}
});
wal.clear().unwrap();
bench_once("wal clear (fsync per record)", n, || {
for _ in 0..n {
wal.clear().unwrap();
}
});
wal.clear().unwrap();
bench_once("wal truncate (no fsync)", n, || {
for _ in 0..n {
wal.truncate().unwrap();
}
});
use std::fs::OpenOptions;
use std::io::Write;
let bytes = vec![0u8; 200];
bench_once(" - open + write + fsync", n, || {
for _ in 0..n {
let mut f = OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.unwrap();
f.write_all(&bytes).unwrap();
f.sync_data().unwrap();
}
});
let mut open_file = OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.unwrap();
bench_once(" - reuse handle: write + fsync", n, || {
for _ in 0..n {
open_file.write_all(&bytes).unwrap();
open_file.sync_data().unwrap();
}
});
bench_once(" - only fsync (no open, no write)", n, || {
for _ in 0..n {
open_file.sync_data().unwrap();
}
});
bench_once(" - only open/close", n, || {
for _ in 0..n {
let _ = OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.unwrap();
}
});
wal.clear().unwrap();
}
/// 复刻 write_via_wal 的端到端写入顺序
fn bench_write_path(dir: &std::path::Path, n: usize) {
let path = dir.join("wp.db");
let idx = dir.join("wp.idx");
let wal_path = dir.join("wp.wal");
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(&idx);
let _ = std::fs::remove_file(&wal_path);
let mut t = HeapTable::open_indexed(
&path,
&idx,
DEFAULT_PAGE_SIZE,
DEFAULT_BTREE_ORDER,
1024,
)
.unwrap();
let wal = Wal::new(&wal_path);
bench_once("write path single INSERT (end to end)", n, || {
for i in 0..n as i64 {
wal.truncate().unwrap();
let op = WalOp::Insert {
table: "t".to_string(),
row: row(i),
};
wal.append(&op).unwrap();
t.insert_row(&row(i)).unwrap();
t.flush().unwrap();
wal.clear().unwrap();
}
});
bench_once(" - wal.truncate() (no fsync)", n, || {
for _ in 0..n {
wal.truncate().unwrap();
}
});
let op = WalOp::Insert {
table: "t".to_string(),
row: row(1),
};
bench_once(" - wal.append() (with fsync)", n, || {
for _ in 0..n {
wal.append(&op).unwrap();
}
});
wal.clear().unwrap();
bench_once(" - wal.clear() (with fsync)", n, || {
for _ in 0..n {
wal.clear().unwrap();
}
});
bench_once(" - heap.insert_row() (incl. index write)", n, || {
for i in n as i64..2 * n as i64 {
t.insert_row(&row(i)).unwrap();
}
});
bench_once(" - heap.flush()", n, || {
for _ in 0..n {
t.flush().unwrap();
}
});
}
fn main() {
let n: usize = std::env::var("CHUSQL_BENCH_N")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(2000);
println!("chusql-storage benchmark: n = {}, page size = {} bytes, btree order = {}",
n, DEFAULT_PAGE_SIZE, DEFAULT_BTREE_ORDER);
println!("best of each round set; fsync entries run once only (repeats are too slow)\n");
let dir = tempfile::tempdir().unwrap();
println!("[page IO]");
bench_page(dir.path(), n);
println!("\n[B+ tree]");
bench_btree(dir.path(), n);
println!("\n[heap]");
bench_heap(dir.path(), n);
println!("\n[WAL]");
bench_wal(dir.path(), n.min(200));
println!("\n[server write path]");
bench_write_path(dir.path(), n.min(200));
}
-12
View File
@@ -1,12 +0,0 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/topics/lock_files
packages: []
snapshots:
- completed:
sha256: 0f728e30e843d7f00460ad56858b995a3b227fca683659c7df9f2fc3988a3ce1
size: 732465
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/59.yaml
original: lts-24.59
@@ -13,12 +13,13 @@ import ChuSQL.Storage.IPC (
doListCatalog,
doLookupByColumn,
sendRequest,
setPipeName,
)
import ChuSQL.Syntax.Parser (parseStatement)
import Control.Concurrent (threadDelay)
import Control.Exception (SomeException, bracket, evaluate, try)
import Control.Monad (filterM, unless, when)
import Data.List (intercalate, isInfixOf, isPrefixOf)
import Data.List (intercalate, isInfixOf)
import GHC.Clock (getMonotonicTime)
import System.Directory (
createDirectoryIfMissing,
@@ -26,9 +27,10 @@ import System.Directory (
getTemporaryDirectory,
removeDirectoryRecursive,
)
import System.Environment (getArgs, getEnvironment, setEnv)
import System.Environment (getArgs)
import System.Exit (exitFailure)
import System.FilePath ((</>))
import System.Info (os)
import System.Process (
CreateProcess (..),
ProcessHandle,
@@ -40,21 +42,19 @@ import System.Process (
)
import Text.Printf (printf)
-- 全链路基准:Haskell 引擎与 Rust 存储进程一起跑,
-- 语句走完整链路(解析、优化、命名管道、WAL、落盘),
-- 用墙上时钟计时,结果打印到标准输出。
-- 全链路基准:Haskell 引擎与 Rust 存储进程一起跑,语句走完整链路并打印墙上耗时。
-- | 找 Rust 存储进程:优先 release
findServer :: FilePath -> IO FilePath
findServer "" = do
let dirs =
[ "chusql-storage/target"
, "../../chusql-storage/target"
, "../chusql-storage/target"
, "target"
]
modes = ["release", "debug"]
exes = ["chusql-storage.exe", "chusql-storage"]
-- 名字跟平台走:同一个 target 目录里可能躺着一份别处交叉编译出来的 .exe,
-- 在 Linux 上误选它只会拿到 config file not found
exes = [if os == "mingw32" then "chusql-storage.exe" else "chusql-storage"]
candidates = [d </> m </> e | d <- dirs, m <- modes, e <- exes]
found <- filterM doesFileExist candidates
case found of
@@ -74,27 +74,12 @@ findServer p = do
putStrLn ("storage binary not found: " ++ p)
exitFailure
-- | 子进程环境:继承并覆盖 CHUSQL_*
childEnv :: [(String, String)] -> IO [(String, String)]
childEnv extra = do
base <- getEnvironment
let stripped = filter (\(k, _) -> not ("CHUSQL_" `isPrefixOf` k)) base
pure (stripped ++ extra)
-- | 起存储进程并等管道可连
startServer :: FilePath -> String -> FilePath -> IO ProcessHandle
startServer serverPath pipe dataDir = do
envs <-
childEnv
[ ("CHUSQL_PIPE", pipe)
, ("CHUSQL_DATA_DIR", dataDir)
, ("CHUSQL_PAGE_SIZE", "4096")
, ("CHUSQL_BTREE_ORDER", "4")
, ("CHUSQL_LOG", "error")
]
-- | 起存储进程并等管道可连:调参走 --config 指的那份配置文件,环境原样继承
startServer :: FilePath -> FilePath -> IO ProcessHandle
startServer serverPath configPath = do
(_, _, _, ph) <-
createProcess
(proc serverPath []) {env = Just envs, std_out = NoStream, std_err = Inherit}
(proc serverPath ["--config", configPath]){std_out = NoStream, std_err = Inherit}
ready <- waitReady (200 :: Int)
unless ready $ do
putStrLn "storage process was not ready within 5 s"
@@ -194,12 +179,12 @@ batchInsert label n mkSql = do
-- | 一次点查(原始 IPC,不经过引擎)
benchLookup :: String -> String -> Int -> Int -> IO ()
benchLookup table column key times = do
_ <- doLookupByColumn table column key
_ <- doLookupByColumn table column (VInt key)
t0 <- getMonotonicTime
results <- mapM (const (doLookupByColumn table column key)) [1 .. times]
results <- mapM (const (doLookupByColumn table column (VInt key))) [1 .. times]
t1 <- getMonotonicTime
let total = t1 - t0
hit = length [() | Right (IndexRow (Just _)) <- results]
hit = length [() | Right (IndexRows (_ : _)) <- results]
printf
"%-38s %8.1f ms %7.3f ms/op (hits %d/%d)\n"
"lookup_by_index (raw IPC, by id)"
@@ -255,7 +240,6 @@ runBatch sqls = do
chunked :: Int -> Int -> [[Int]]
chunked size n = go [1 .. n]
where
-- | 每次切下 size 个
go [] = []
go xs = let (a, b) = splitAt size xs in a : go b
@@ -391,11 +375,26 @@ main = do
stamp <- getMonotonicTime
let pipe = "chusql-fullchain-" ++ show (round (stamp * 1e6) :: Int)
dataDir = tmp </> pipe
configPath = tmp </> (pipe ++ ".toml")
-- TOML 的普通字符串会吃反斜杠,路径统一用正斜杠
slashed = map (\c -> if c == '\\' then '/' else c) dataDir
createDirectoryIfMissing True dataDir
setEnv "CHUSQL_PIPE" pipe
writeFile
configPath
( unlines
[ "[storage]"
, "data_dir = \"" ++ slashed ++ "\""
, "[server]"
, "pipe_name = \"" ++ pipe ++ "\""
, "[log]"
, "level = \"error\""
]
)
setPipeName pipe
printf "full-chain benchmark: Haskell engine <--named pipe--> Rust storage\n"
printf "storage : %s\n" serverPath
printf "config : %s\n" configPath
printf "pipe : %s\n" pipe
printf "data dir : %s\n" dataDir
printf "dataset : users = %d rows, orders = %d rows\n" u o
@@ -404,7 +403,7 @@ main = do
bracket
(do
t1 <- getMonotonicTime
ph <- startServer serverPath pipe dataDir
ph <- startServer serverPath configPath
t2 <- getMonotonicTime
printf "\n[startup] storage ready in %.2f s\n" (t2 - t1)
pure ph
@@ -2,4 +2,4 @@ resolver: lts-24.59
packages:
- .
- ../../chusql-engine
- ../chusql-engine
File renamed without changes.