セットアップは 2 つあります。1 つは Windows 10 (ntfs パーティション) で実行され、もう 1 つは Debian (ext4 パーティション) で実行されます。R のソース コードは同じです。メイン プロセスは 8 つの子プロセス (P-SOCKS) を 8 つの仮想コアで開始し、それらはすべて同じ WAL 対応の sqlite データベースに対してクエリと書き込みを実行します。
Windows 10 では、すべてのプロセスに分散された 100 % の CPU 負荷が発生します。Debian では、CPU 負荷が 25 % になることはほとんどありません。Debian のプロセスを監視すると、一度に 1 つのプロセスのみが vcore で 100 % に達するため、書き込みがボトルネックになっていると考えられます。(他のプロセスは書き込みを待機している可能性があります。)
各接続はPRAGMA busy_timeout = 60000;
およびを使用していますPRAGMA journal_mode = WAL;
。
PRAGMA synchronous = OFF;
これをデバッグしようとしています。と関係があるのではないかと考えてみましたがfsync()
、改善は見られません。Debian でパフォーマンスが低い原因について、他に何か提案はありますか?
編集:
SCSI ディスクで Write-Cache が有効になっているようです ( で確認済み)。また、やsdparm
などの ext4 マウント オプションを調整しても効果がないようです。barrier=0
data=writeback
ベンチマーク
以下は同時書き込みのベンチマークを行うための簡単なコードです。
make.con <- function() {
con <<- DBI::dbConnect(RSQLite::SQLite(), dbname = 'db.sqlite')
DBI::dbExecute(con, 'PRAGMA journal_mode = WAL;')
DBI::dbExecute(con, 'PRAGMA busy_timeout = 60000;')
DBI::dbExecute(con, '
CREATE TABLE IF NOT EXISTS tmp (
id INTEGER NOT NULL,
blob BLOB NOT NULL,
PRIMARY KEY (id)
)')
}
make.con()
fn <- function(x) {
set.seed(x)
# read
random.blob.read <- RSQLite::dbGetQuery(con, 'SELECT blob FROM tmp WHERE id = (SELECT abs(random() % max(tm.id)) FROM tmp tm);')
# write
blob <- serialize(list(rand = runif(1000)), connection = NULL, xdr = FALSE)
RSQLite::dbExecute(con, 'INSERT INTO tmp (blob) VALUES (:blob);', params = list('blob' = list(blob)))
}
n <- 30000L
parallel::setDefaultCluster(parallel::makeCluster(spec = 2L))
parallel::clusterExport(varlist = 'make.con')
invisible(parallel::clusterEvalQ(expr = {make.con()}))
microbenchmark::microbenchmark(
lapply(1:n, fn),
parallel::parLapplyLB(X = 1:n, fun = fn, chunk.size = 50L),
times = 2L
)
parallel::stopCluster(cl = parallel::getDefaultCluster())
コードは、データベースに BLOB を読み書きするだけです。まず、ダミー実行をいくつか実行し、データベースを数 GB まで増やします。
私の Windows 10 ラップトップでは、次の結果が得られます (6 GB のデータベース)。
Unit: seconds
expr min lq mean median uq max neval
lapply(1:n, fn) 26.02392 26.02392 26.54853 26.54853 27.07314 27.07314 2
parallel::parLapplyLB(X = 1:n, fun = fn, chunk.size = 50L) 15.73851 15.73851 16.44554 16.44554 17.15257 17.15257 2
1 つの vcore が 100% で、次に 2 つの vcore が 100% であることがはっきりとわかります。パフォーマンスはほぼ 2 倍速く、2 つの同時プロセスが互いにブロックしていないことがわかります。
Debian では次のようになります:
Unit: seconds
expr min lq mean median uq max neval
lapply(1:n, fn) 39.96850 39.96850 40.14782 40.14782 40.32714 40.32714 2
parallel::parLapplyLB(X = 1:n, fun = fn, chunk.size = 50L) 43.34628 43.34628 44.85910 44.85910 46.37191 46.37191 2
2 つの vcore が最大になることはありません。また、2 つのプロセスを使用してもパフォーマンスは向上しません。プロセスが互いにブロックし合うため、さらに悪化します。最後に、Debian はより優れた (仮想化されているとはいえ) ハードウェアを使用しています。
答え1
Ubuntu 18.04 で確認済みですが、Windows ではテストしていません。
例を簡略化し、インストルメンテーション コードを追加しました。最初のプロットは、各サブプロセスに書き込まれた BLOB の数を示しています。最初のプロットでは、プラトーは約 0.2 秒間すべてのコアで非アクティブであることを示し、急激な上昇はすべてのコアにわたるバースト書き込みを示しています。2 番目のプロットは生データを示しています。これは、StackOverflow の回答では機能しない plotly で最も役立ちます。
有効にするとgc()
実行時間は長くなりますが、負荷はより均等に分散されます (下の 2 番目のグラフを参照)。
何が起こっているのか全く分かりません。この設定を再現してさらに実験していただけますか? ここで、または RSQLite の問題追跡ツールでフィードバックをいただければ幸いです。
基本実行、なしgc()
make.con <- function() {
options(digits.secs = 6)
con <<- DBI::dbConnect(RSQLite::SQLite(), dbname = "db.sqlite")
DBI::dbExecute(con, "PRAGMA journal_mode = WAL;")
DBI::dbExecute(con, "PRAGMA busy_timeout = 60000;")
DBI::dbExecute(con, "PRAGMA synchronous = OFF;")
DBI::dbExecute(con, "
CREATE TABLE IF NOT EXISTS tmp (
id INTEGER NOT NULL,
blob BLOB NOT NULL,
PRIMARY KEY (id)
)")
}
make.con()
#> [1] 0
blob <- serialize(list(rand = runif(1000)), connection = NULL, xdr = FALSE)
fn <- function(x) {
time0 <- Sys.time()
rs <- DBI::dbSendQuery(con, "INSERT INTO tmp (blob) VALUES (:blob);")
time1 <- Sys.time()
DBI::dbBind(rs, params = list("blob" = list(blob)))
time2 <- Sys.time()
DBI::dbClearResult(rs)
time3 <- Sys.time()
# gc()
time4 <- Sys.time()
list(pid = unix::getpid(), time0 = time0, time1 = time1, time2 = time2, time3 = time3, time4 = time4)
}
n <- 1000L
parallel::setDefaultCluster(parallel::makeCluster(8L))
parallel::clusterExport(varlist = c("make.con", "blob"))
invisible(parallel::clusterEvalQ(expr = {
make.con()
}))
data <- parallel::parLapply(X = 1:n, fun = fn, chunk.size = 50L)
parallel::stopCluster(cl = parallel::getDefaultCluster())
library(tidyverse)
tbl <-
data %>%
transpose() %>%
map(unlist, recursive = FALSE) %>%
as_tibble() %>%
rowid_to_column() %>%
pivot_longer(-c(rowid, pid), names_to = "step", values_to = "time") %>%
mutate(time = as.POSIXct(time, origin = "1970-01-01")) %>%
mutate(pid = factor(pid)) %>%
arrange(time)
tbl %>%
group_by(pid) %>%
mutate(cum = row_number()) %>%
ungroup() %>%
ggplot(aes(x = time, y = cum, color = pid)) +
geom_line()
p <-
tbl %>%
ggplot(aes(x = time, y = factor(pid), group = 1)) +
geom_path() +
geom_point(aes(color = step))
p
plotly::ggplotly(p)
(plotly は StackOverflow では動作しません)
2020-01-30に作成reprex パッケージ(v0.3.0)