大更新:
1、完善SQL语法 2、移除node前端,只保留静态资源 3、写好安装脚本 4、做好linux适配
This commit is contained in:
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
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
resolver: lts-24.59
|
||||
|
||||
packages:
|
||||
- .
|
||||
- ../../chusql-engine
|
||||
File renamed without changes.
Generated
-334
@@ -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"
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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.
Reference in new issue
Block a user