From 892756bcb86d2dfd1cc5337d6482ac6d76da65e7 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:22:38 +0100 Subject: [PATCH 01/41] Null change --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2466604b9..b3f914a8a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: TreeTools Title: Create, Modify and Analyse Phylogenetic Trees -Version: 1.15.0.9006 +Version: 1.15.0.9006.0000 Authors@R: c( person("Martin R.", 'Smith', role = c("aut", "cre", "cph"), email = "martin.smith@durham.ac.uk", From 4ac8546e53a49edc8a0fda1f876e9a66350a91c5 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:12:44 +0100 Subject: [PATCH 02/41] Check system state; pkill; switch order --- .github/workflows/benchmark.yml | 44 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index affdd845e..457020458 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -54,33 +54,61 @@ jobs: with: dependencies: '"hard"' needs: benchmark - + + - name: Check system state before PR1 + run: | + cat /proc/cpuinfo | grep "cpu MHz" | head -4 + free -h + uptime + - name: Benchmark PR working-directory: pr run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + # Kill any R processes and clear R temp files + pkill -f "R --" || true + rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R mkdir ./pr-benchmark-results/ mv *.bench.Rds ./pr-benchmark-results/ shell: bash - - name: Benchmark main - working-directory: main + - name: Check system state before PR2 run: | - R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - Rscript benchmark/_run_benchmarks.R - mkdir ../pr/main-benchmark-results/ - mv *.bench.Rds ../pr/main-benchmark-results/ - shell: bash + cat /proc/cpuinfo | grep "cpu MHz" | head -4 + free -h + uptime - name: Benchmark PR working-directory: pr run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + # Kill any R processes and clear R temp files + pkill -f "R --" || true + rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R mkdir ./pr2-benchmark-results/ mv *.bench.Rds ./pr2-benchmark-results/ shell: bash + + - name: Check system state before main + run: | + cat /proc/cpuinfo | grep "cpu MHz" | head -4 + free -h + uptime + + - name: Benchmark main + working-directory: main + run: | + R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + # Kill any R processes and clear R temp files + pkill -f "R --" || true + rm -rf /tmp/R* || true + Rscript benchmark/_run_benchmarks.R + mkdir ../pr/main-benchmark-results/ + mv *.bench.Rds ../pr/main-benchmark-results/ + shell: bash + - run: dir pr-benchmark-results working-directory: pr From 9734afa0f1aa66f61e1da7298f07f1125ee2c8ea Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:15:31 +0100 Subject: [PATCH 03/41] Run benchmarks in separate processes --- benchmark/_run_benchmarks.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benchmark/_run_benchmarks.R b/benchmark/_run_benchmarks.R index 0f7039196..41a31efcf 100644 --- a/benchmark/_run_benchmarks.R +++ b/benchmark/_run_benchmarks.R @@ -1,2 +1,4 @@ -source("benchmark/_init.R") -sapply(list.files("benchmark", "^bench\\-.*\\.R$", full.names = TRUE), source) +benchFiles <- list.files("benchmark", "^bench\\-.*\\.R$", full.names = TRUE) +for (benchFile in benchFiles) { + system2("Rscript", c("-e", paste0("source('", benchFile, "')"))) +} From dba8d2c723d16e80eb6af9363d7ca03a2032a17f Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:29:14 +0100 Subject: [PATCH 04/41] single ''s --- .github/workflows/benchmark.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 457020458..f67679f2c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -66,7 +66,7 @@ jobs: run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch # Kill any R processes and clear R temp files - pkill -f "R --" || true + pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R mkdir ./pr-benchmark-results/ @@ -84,7 +84,7 @@ jobs: run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch # Kill any R processes and clear R temp files - pkill -f "R --" || true + pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R mkdir ./pr2-benchmark-results/ @@ -102,7 +102,7 @@ jobs: run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch # Kill any R processes and clear R temp files - pkill -f "R --" || true + pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R mkdir ../pr/main-benchmark-results/ From 0898f12b29a39f1729cd093e2cf70ac26deea3ed Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:50:41 +0100 Subject: [PATCH 05/41] debug --- .github/workflows/benchmark.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f67679f2c..61a6c0e1b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -65,8 +65,10 @@ jobs: working-directory: pr run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - # Kill any R processes and clear R temp files - pkill -f 'R --' || true + echo 1234 + pkill -f Rscript || true + pkill R || true + echo 5678 rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R mkdir ./pr-benchmark-results/ From bb831e7346cd5cc1c8b2956e7750953f12f0d1f9 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:07:19 +0100 Subject: [PATCH 06/41] shQuote --- benchmark/_run_benchmarks.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/_run_benchmarks.R b/benchmark/_run_benchmarks.R index 41a31efcf..c62c15b99 100644 --- a/benchmark/_run_benchmarks.R +++ b/benchmark/_run_benchmarks.R @@ -1,4 +1,4 @@ benchFiles <- list.files("benchmark", "^bench\\-.*\\.R$", full.names = TRUE) for (benchFile in benchFiles) { - system2("Rscript", c("-e", paste0("source('", benchFile, "')"))) + system2("Rscript", c("-e", shQuote(paste0("source('", benchFile, "')")))) } From 1c3e93eaffee59b5a02eac4e3569e2e3226a1505 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:27:37 +0100 Subject: [PATCH 07/41] Clean --- .github/workflows/benchmark.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 61a6c0e1b..c86608542 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -65,12 +65,11 @@ jobs: working-directory: pr run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - echo 1234 - pkill -f Rscript || true - pkill R || true - echo 5678 rm -rf /tmp/R* || true + rm -rf /tmp/R_sess* || true + echo 'Start benchmark run' Rscript benchmark/_run_benchmarks.R + echo 'Benchmark run complete' mkdir ./pr-benchmark-results/ mv *.bench.Rds ./pr-benchmark-results/ shell: bash From c2e4f59014215e48c3415eb895bffb2903ab1dfa Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:56:51 +0100 Subject: [PATCH 08/41] pr-main-pr --- .github/workflows/benchmark.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c86608542..28cb10d3e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -74,43 +74,42 @@ jobs: mv *.bench.Rds ./pr-benchmark-results/ shell: bash - - name: Check system state before PR2 + - name: Check system state before main run: | cat /proc/cpuinfo | grep "cpu MHz" | head -4 free -h uptime - - name: Benchmark PR - working-directory: pr + - name: Benchmark main + working-directory: main run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch # Kill any R processes and clear R temp files pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R - mkdir ./pr2-benchmark-results/ - mv *.bench.Rds ./pr2-benchmark-results/ + mkdir ../pr/main-benchmark-results/ + mv *.bench.Rds ../pr/main-benchmark-results/ shell: bash - - name: Check system state before main + - name: Check system state before PR2 run: | cat /proc/cpuinfo | grep "cpu MHz" | head -4 free -h uptime - - name: Benchmark main - working-directory: main + - name: Benchmark PR + working-directory: pr run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch # Kill any R processes and clear R temp files pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R - mkdir ../pr/main-benchmark-results/ - mv *.bench.Rds ../pr/main-benchmark-results/ + mkdir ./pr2-benchmark-results/ + mv *.bench.Rds ./pr2-benchmark-results/ shell: bash - - run: dir pr-benchmark-results working-directory: pr - run: dir main-benchmark-results From 5737e391ef127ef2af79bfc5e3cb5f5cb0d9f0c8 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 06:04:53 +0100 Subject: [PATCH 09/41] main 1st? --- .github/workflows/benchmark.yml | 35 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 28cb10d3e..2e1e79edc 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -55,43 +55,44 @@ jobs: dependencies: '"hard"' needs: benchmark - - name: Check system state before PR1 + - name: Check system state before main run: | cat /proc/cpuinfo | grep "cpu MHz" | head -4 free -h uptime - - - name: Benchmark PR - working-directory: pr + + - name: Benchmark main + working-directory: main run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + # Kill any R processes and clear R temp files + pkill -f 'R --' || true rm -rf /tmp/R* || true - rm -rf /tmp/R_sess* || true - echo 'Start benchmark run' Rscript benchmark/_run_benchmarks.R - echo 'Benchmark run complete' - mkdir ./pr-benchmark-results/ - mv *.bench.Rds ./pr-benchmark-results/ + mkdir ../pr/main-benchmark-results/ + mv *.bench.Rds ../pr/main-benchmark-results/ shell: bash - - name: Check system state before main + - name: Check system state before PR1 run: | cat /proc/cpuinfo | grep "cpu MHz" | head -4 free -h uptime - - - name: Benchmark main - working-directory: main + + - name: Benchmark PR + working-directory: pr run: | R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - # Kill any R processes and clear R temp files - pkill -f 'R --' || true rm -rf /tmp/R* || true + rm -rf /tmp/R_sess* || true + echo 'Start benchmark run' Rscript benchmark/_run_benchmarks.R - mkdir ../pr/main-benchmark-results/ - mv *.bench.Rds ../pr/main-benchmark-results/ + echo 'Benchmark run complete' + mkdir ./pr-benchmark-results/ + mv *.bench.Rds ./pr-benchmark-results/ shell: bash + - name: Check system state before PR2 run: | cat /proc/cpuinfo | grep "cpu MHz" | head -4 From fc0fb6c7d88db4533755ef0407218407796aad26 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 06:12:07 +0100 Subject: [PATCH 10/41] Report hashes --- benchmark/bench-DropTip.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmark/bench-DropTip.R b/benchmark/bench-DropTip.R index 636467e2e..e6f5fa3ea 100644 --- a/benchmark/bench-DropTip.R +++ b/benchmark/bench-DropTip.R @@ -1,7 +1,9 @@ source("benchmark/_init.R") # sets seed tr80 <- rtree(80) +cat("Tree hash 80:", digest::digest(tr80), "\n") tr2000 <- rtree(2000) +cat("Tree hash 2000:", digest::digest(tr2000), "\n") Benchmark("DropTip.80", ub(DropTip(tr80, 5))) Benchmark("DropTip.2000", ub(DropTip(tr2000, 5), times = 25)) From 40b719a364a31cd51e69499ce372fc8d52a17795 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 06:12:15 +0100 Subject: [PATCH 11/41] same v no --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index b3f914a8a..2466604b9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: TreeTools Title: Create, Modify and Analyse Phylogenetic Trees -Version: 1.15.0.9006.0000 +Version: 1.15.0.9006 Authors@R: c( person("Martin R.", 'Smith', role = c("aut", "cre", "cph"), email = "martin.smith@durham.ac.uk", From 2dce1564bab6a59b5646882bd151dd082c8cfd41 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 06:14:11 +0100 Subject: [PATCH 12/41] Fix tree topologies in case of rtree differences --- benchmark/bench-DropTip.R | 15 +++++++++++---- benchmark/tr2000.rds | Bin 0 -> 43718 bytes benchmark/tr80.rds | Bin 0 -> 1789 bytes 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 benchmark/tr2000.rds create mode 100644 benchmark/tr80.rds diff --git a/benchmark/bench-DropTip.R b/benchmark/bench-DropTip.R index e6f5fa3ea..699d57e04 100644 --- a/benchmark/bench-DropTip.R +++ b/benchmark/bench-DropTip.R @@ -1,9 +1,16 @@ source("benchmark/_init.R") # sets seed -tr80 <- rtree(80) -cat("Tree hash 80:", digest::digest(tr80), "\n") -tr2000 <- rtree(2000) -cat("Tree hash 2000:", digest::digest(tr2000), "\n") +if (!file.exists("benchmark/tr80.rds")) { + set.seed(1337) + tr80 <- rtree(80) + tr2000 <- rtree(2000) + saveRDS(tr80, "benchmark/tr80.rds") + saveRDS(tr2000, "benchmark/tr2000.rds") +} + +tr80 <- readRDS("benchmark/tr80.rds") +tr2000 <- readRDS("benchmark/tr2000.rds") + Benchmark("DropTip.80", ub(DropTip(tr80, 5))) Benchmark("DropTip.2000", ub(DropTip(tr2000, 5), times = 25)) diff --git a/benchmark/tr2000.rds b/benchmark/tr2000.rds new file mode 100644 index 0000000000000000000000000000000000000000..024c0c501470fca74f58f23d40d0706341616f39 GIT binary patch literal 43718 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 literal 0 HcmV?d00001 From 50b22fbea80e3b4fe6583530ee04b3ce0b93b025 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 07:54:37 +0100 Subject: [PATCH 13/41] ChatGPT suggestions --- .github/workflows/benchmark.yml | 46 +++++++++++---------------------- benchmark/_run_benchmarks.R | 2 +- benchmark/bench-DropTip.R | 2 +- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 2e1e79edc..adecd8711 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -55,58 +55,42 @@ jobs: dependencies: '"hard"' needs: benchmark - - name: Check system state before main - run: | - cat /proc/cpuinfo | grep "cpu MHz" | head -4 - free -h - uptime - - - name: Benchmark main - working-directory: main - run: | - R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - # Kill any R processes and clear R temp files - pkill -f 'R --' || true - rm -rf /tmp/R* || true - Rscript benchmark/_run_benchmarks.R - mkdir ../pr/main-benchmark-results/ - mv *.bench.Rds ../pr/main-benchmark-results/ - shell: bash - - - name: Check system state before PR1 - run: | - cat /proc/cpuinfo | grep "cpu MHz" | head -4 - free -h - uptime - - name: Benchmark PR working-directory: pr run: | - R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + R CMD INSTALL . --library=$(mktemp -d) --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch rm -rf /tmp/R* || true rm -rf /tmp/R_sess* || true echo 'Start benchmark run' + lsof -p $(pgrep R) | grep '\.so' # check what shared objects are loaded Rscript benchmark/_run_benchmarks.R echo 'Benchmark run complete' mkdir ./pr-benchmark-results/ mv *.bench.Rds ./pr-benchmark-results/ shell: bash - - - name: Check system state before PR2 + - name: Benchmark main + working-directory: main run: | - cat /proc/cpuinfo | grep "cpu MHz" | head -4 - free -h - uptime + R CMD INSTALL . --library=$(mktemp -d) --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + # Kill any R processes and clear R temp files + pkill -f 'R --' || true + rm -rf /tmp/R* || true + lsof -p $(pgrep R) | grep '\.so' # check what shared objects are loaded + Rscript benchmark/_run_benchmarks.R + mkdir ../pr/main-benchmark-results/ + mv *.bench.Rds ../pr/main-benchmark-results/ + shell: bash - name: Benchmark PR working-directory: pr run: | - R CMD INSTALL . --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch + R CMD INSTALL . --library=$(mktemp -d) --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch # Kill any R processes and clear R temp files pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R + lsof -p $(pgrep R) | grep '\.so' # check what shared objects are loaded mkdir ./pr2-benchmark-results/ mv *.bench.Rds ./pr2-benchmark-results/ shell: bash diff --git a/benchmark/_run_benchmarks.R b/benchmark/_run_benchmarks.R index c62c15b99..625de261c 100644 --- a/benchmark/_run_benchmarks.R +++ b/benchmark/_run_benchmarks.R @@ -1,4 +1,4 @@ benchFiles <- list.files("benchmark", "^bench\\-.*\\.R$", full.names = TRUE) -for (benchFile in benchFiles) { +for (benchFile in sample(benchFiles)) { system2("Rscript", c("-e", shQuote(paste0("source('", benchFile, "')")))) } diff --git a/benchmark/bench-DropTip.R b/benchmark/bench-DropTip.R index 699d57e04..e4a01f8b3 100644 --- a/benchmark/bench-DropTip.R +++ b/benchmark/bench-DropTip.R @@ -11,7 +11,7 @@ if (!file.exists("benchmark/tr80.rds")) { tr80 <- readRDS("benchmark/tr80.rds") tr2000 <- readRDS("benchmark/tr2000.rds") -Benchmark("DropTip.80", ub(DropTip(tr80, 5))) +Benchmark("DropTip.80", ub(replicate(50, DropTip(tr80, 5)))) Benchmark("DropTip.2000", ub(DropTip(tr2000, 5), times = 25)) unlen80 <- tr80 From 443e05a27668407a267a202f410845ec3d661450 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:08:40 +0100 Subject: [PATCH 14/41] use bench::press --- DESCRIPTION | 2 +- benchmark/_compare_results.R | 6 +++--- benchmark/_init.R | 2 +- benchmark/bench-Consensus.R | 12 ++++++++++-- benchmark/bench-DropTip.R | 5 ++++- benchmark/bench-SplitList.R | 8 ++++---- benchmark/bench-renumber_tree.h.R | 12 ++++++------ 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2466604b9..17af2c12b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -62,7 +62,7 @@ Suggests: TreeDist, TreeSearch, vdiffr (>= 1.0.0), -Config/Needs/benchmark: microbenchmark, TreeDist +Config/Needs/benchmark: bench, TreeDist Config/Needs/check: rcmdcheck Config/Needs/coverage: covr Config/Needs/memcheck: devtools diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 05f1af5ad..684af6c88 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -32,9 +32,9 @@ for (pr_file in pr_files) { # Iterate over each function benchmarked for (fn_name in unique(pr_df$expr)) { - pr_times <- pr_df[pr_df$expr == fn_name, "time"] - cf_times <- cf_df[cf_df$expr == fn_name, "time"] - main_times <- main_df[main_df$expr == fn_name, "time"] + pr_times <- as.numeric(pr_df$`itr_time`, units = "us") + cf_times <- as.numeric(cf_df$`itr_time`, units = "us") + main_times <- as.numeric(main_df$`itr_time`, units = "us") matched <- if (length(main_times)) { TRUE } else { diff --git a/benchmark/_init.R b/benchmark/_init.R index 918919e2a..a1075685d 100644 --- a/benchmark/_init.R +++ b/benchmark/_init.R @@ -1,5 +1,5 @@ library("TreeTools") -ub <- microbenchmark::microbenchmark +ub <- bench::mark set.seed(1337) diff --git a/benchmark/bench-Consensus.R b/benchmark/bench-Consensus.R index e29a1a9bb..72ef6f6b1 100644 --- a/benchmark/bench-Consensus.R +++ b/benchmark/bench-Consensus.R @@ -3,8 +3,16 @@ source("benchmark/_init.R") forest1 <- as.phylo(0:200, 80) forest2 <- as.phylo(0:20, 260) +forest3 <- as.phylo(0:1000, 888) -Benchmark("consensus1", ub(Consensus(forest1), times = 250)) -Benchmark("consensus2", ub(Consensus(forest2), times = 500)) +Benchmark("consensus1", ub(Consensus(forest1))) +Benchmark("consensus2", ub(Consensus(forest2))) +Benchmark("consensus3", ub(Consensus(forest3))) # Benchmark("RF1", ub(RobinsonFoulds(forest1), times = 42)) # Benchmark("RF2", ub(RobinsonFoulds(forest2), times = 250)) +devtools::dev_mode() + + microbenchmark::microbenchmark(Consensus(trs), # 434 → 404 → 43! + consensus(trs), # 2000... + times = c(12, 1)) +} diff --git a/benchmark/bench-DropTip.R b/benchmark/bench-DropTip.R index e4a01f8b3..447e9406e 100644 --- a/benchmark/bench-DropTip.R +++ b/benchmark/bench-DropTip.R @@ -11,8 +11,11 @@ if (!file.exists("benchmark/tr80.rds")) { tr80 <- readRDS("benchmark/tr80.rds") tr2000 <- readRDS("benchmark/tr2000.rds") +# Warm-up call +DropTip(tr2000, 5) + Benchmark("DropTip.80", ub(replicate(50, DropTip(tr80, 5)))) -Benchmark("DropTip.2000", ub(DropTip(tr2000, 5), times = 25)) +Benchmark("DropTip.2000", ub(DropTip(tr2000, 5))) unlen80 <- tr80 unlen80$edge.length <- NULL diff --git a/benchmark/bench-SplitList.R b/benchmark/bench-SplitList.R index 3f5b49c43..8eb6e9c2a 100644 --- a/benchmark/bench-SplitList.R +++ b/benchmark/bench-SplitList.R @@ -3,11 +3,11 @@ source("benchmark/_init.R") bigTrees <- as.phylo(1:10, 64*32) someTrees <- as.phylo(1:240, 64*3) -Benchmark("as.Splits192", ub(as.Splits(someTrees), times = 100)) # 16.5 -Benchmark("as.Splits64.32", ub(as.Splits(bigTrees), times = 100)) # 23.4 +Benchmark("as.Splits192", ub(as.Splits(someTrees))) # 16.5 +Benchmark("as.Splits64.32", ub(as.Splits(bigTrees))) # 23.4 bigSplits <- as.Splits(bigTrees) someSplits <- as.Splits(someTrees) -Benchmark("as.phylo.some", ub(lapply(someSplits, as.phylo), times = 180)) # 16.7 -Benchmark("as.phylo.big", ub(lapply(bigSplits, as.phylo), times = 180)) # 34.0 +Benchmark("as.phylo.some", ub(lapply(someSplits, as.phylo))) # 16.7 +Benchmark("as.phylo.big", ub(lapply(bigSplits, as.phylo))) # 34.0 diff --git a/benchmark/bench-renumber_tree.h.R b/benchmark/bench-renumber_tree.h.R index fbfd0d0ee..406ef38ff 100644 --- a/benchmark/bench-renumber_tree.h.R +++ b/benchmark/bench-renumber_tree.h.R @@ -28,9 +28,9 @@ Benchmark("postorder-npec40", ub(TreeTools:::postorder_order(npec40))) pec40k <- BalancedTree(40000)$edge dpec40k <- Disorder(pec40k) npec40k <- NodeNumber(dpec40k) -Benchmark("postorder-pec40k", ub(TreeTools:::postorder_order(pec40k), times = 25)) -Benchmark("postorder-dpec40k", ub(TreeTools:::postorder_order(dpec40k), times = 25)) -Benchmark("postorder-npec40k", ub(TreeTools:::postorder_order(npec40k), times = 25)) +Benchmark("postorder-pec40k", ub(TreeTools:::postorder_order(pec40k))) +Benchmark("postorder-dpec40k", ub(TreeTools:::postorder_order(dpec40k))) +Benchmark("postorder-npec40k", ub(TreeTools:::postorder_order(npec40k))) pec40 <- PectinateTree(40)$edge dpec40 <- Disorder(pec40) @@ -41,9 +41,9 @@ Benchmark("postorder-npec40", ub(TreeTools:::postorder_order(npec40))) pec40k <- PectinateTree(40000)$edge dpec40k <- Disorder(pec40k) npec40k <- NodeNumber(dpec40k) -Benchmark("postorder-pec40k", ub(TreeTools:::postorder_order(pec40k), times = 25)) -Benchmark("postorder-dpec40k", ub(TreeTools:::postorder_order(dpec40k), times = 25)) -Benchmark("postorder-npec40k", ub(TreeTools:::postorder_order(npec40k), times = 25)) +Benchmark("postorder-pec40k", ub(TreeTools:::postorder_order(pec40k))) +Benchmark("postorder-dpec40k", ub(TreeTools:::postorder_order(dpec40k))) +Benchmark("postorder-npec40k", ub(TreeTools:::postorder_order(npec40k))) r80 <- RandomTree(80, root = TRUE) rnd80 <- r80$edge From f68de4c877d81b065749279a1403b0321b63db1c Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:11:18 +0100 Subject: [PATCH 15/41] rm cruft --- benchmark/bench-Consensus.R | 6 ------ 1 file changed, 6 deletions(-) diff --git a/benchmark/bench-Consensus.R b/benchmark/bench-Consensus.R index 72ef6f6b1..9a60eec6e 100644 --- a/benchmark/bench-Consensus.R +++ b/benchmark/bench-Consensus.R @@ -10,9 +10,3 @@ Benchmark("consensus2", ub(Consensus(forest2))) Benchmark("consensus3", ub(Consensus(forest3))) # Benchmark("RF1", ub(RobinsonFoulds(forest1), times = 42)) # Benchmark("RF2", ub(RobinsonFoulds(forest2), times = 250)) -devtools::dev_mode() - - microbenchmark::microbenchmark(Consensus(trs), # 434 → 404 → 43! - consensus(trs), # 2000... - times = c(12, 1)) -} From 99ae4e7b9fe2a356d1bcafc532bcf0a0afe61ed3 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:25:08 +0100 Subject: [PATCH 16/41] -lsof --- .github/workflows/benchmark.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index adecd8711..4817be5ad 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -62,7 +62,6 @@ jobs: rm -rf /tmp/R* || true rm -rf /tmp/R_sess* || true echo 'Start benchmark run' - lsof -p $(pgrep R) | grep '\.so' # check what shared objects are loaded Rscript benchmark/_run_benchmarks.R echo 'Benchmark run complete' mkdir ./pr-benchmark-results/ @@ -76,7 +75,6 @@ jobs: # Kill any R processes and clear R temp files pkill -f 'R --' || true rm -rf /tmp/R* || true - lsof -p $(pgrep R) | grep '\.so' # check what shared objects are loaded Rscript benchmark/_run_benchmarks.R mkdir ../pr/main-benchmark-results/ mv *.bench.Rds ../pr/main-benchmark-results/ @@ -90,7 +88,6 @@ jobs: pkill -f 'R --' || true rm -rf /tmp/R* || true Rscript benchmark/_run_benchmarks.R - lsof -p $(pgrep R) | grep '\.so' # check what shared objects are loaded mkdir ./pr2-benchmark-results/ mv *.bench.Rds ./pr2-benchmark-results/ shell: bash From b43a3e6e7ab3ce4f932fc170efd4c7f60d370ade Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:43:22 +0100 Subject: [PATCH 17/41] Artefacts --- .github/workflows/benchmark.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 4817be5ad..62f70d46c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -104,6 +104,25 @@ jobs: Rscript benchmark/_compare_results.R shell: bash + - name: Upload PR benchmark results + if: failure() + uses: actions/upload-artifact@v4 + working-directory: pr + with: + name: pr-benchmark-results + path: | + pr-benchmark-results/*.bench.Rds + + - name: Upload main benchmark results + if: failure() + uses: actions/upload-artifact@v4 + working-directory: pr + with: + name: main-benchmark-results + path: | + main-benchmark-results/*.bench.Rds + + - name: Comment on PR if: always() && github.event_name == 'pull_request' && steps.compare_results.outputs.report != '' uses: actions/github-script@v7 From e11ec0e1856c172360da6fec202bd005c89874a7 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:46:31 +0100 Subject: [PATCH 18/41] Ignore benchmark results --- .Rbuildignore | 1 + .gitignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.Rbuildignore b/.Rbuildignore index faab9b50d..4abb721ae 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -21,6 +21,7 @@ vignettes/.RData ^\.?R+prof.*$ ^\.zenodo\.json$ /^benchmark/ +/^*.benchmark-results/ /^src\-/ ^CODE_OF_CONDUCT\.md$ ^codemeta\.json$ diff --git a/.gitignore b/.gitignore index 6b39aeef9..508c5af84 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ vignettes/*_cache* !TreeTools.Rproj *.site *.bak +/*-benchmark-results From d8bf291f15d8f7c17170d5032a0f942051bc1695 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:57:19 +0100 Subject: [PATCH 19/41] bench format --- benchmark/_compare_results.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 684af6c88..04a605917 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -31,10 +31,10 @@ for (pr_file in pr_files) { report <- list() # Iterate over each function benchmarked - for (fn_name in unique(pr_df$expr)) { - pr_times <- as.numeric(pr_df$`itr_time`, units = "us") - cf_times <- as.numeric(cf_df$`itr_time`, units = "us") - main_times <- as.numeric(main_df$`itr_time`, units = "us") + for (fn_name in unique(as.character(pr_df$expr))) { + pr_times <- as.numeric( pr_df[["time"]][[1]]) + cf_times <- as.numeric( cf_df[["time"]][[1]]) + main_times <- as.numeric(main_df[["time"]][[1]]) matched <- if (length(main_times)) { TRUE } else { @@ -96,9 +96,9 @@ for (pr_file in pr_files) { "| `", fn_name, "` | ", status, " | ", bold, round(res$change, 2), "%", bold, "
(p: ", format.pval(res$p_value), ") | ", - signif(res$median_main / 1e6, 3), " \u2192
", - signif(res$median_pr / 1e6, 3), ", ", - signif(res$median_cf / 1e6, 3), " |\n" + signif(res$median_main * 1e3, 3), " \u2192
", + signif(res$median_pr * 1e3, 3), ", ", + signif(res$median_cf * 1e3, 3), " |\n" ) } From fcced56b07009b6411ade2b19d3fe5fd9e816cbc Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:37:27 +0100 Subject: [PATCH 20/41] -wd --- .github/workflows/benchmark.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 62f70d46c..9a79dff07 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -107,11 +107,10 @@ jobs: - name: Upload PR benchmark results if: failure() uses: actions/upload-artifact@v4 - working-directory: pr with: name: pr-benchmark-results path: | - pr-benchmark-results/*.bench.Rds + pr/pr-benchmark-results/*.bench.Rds - name: Upload main benchmark results if: failure() @@ -120,7 +119,7 @@ jobs: with: name: main-benchmark-results path: | - main-benchmark-results/*.bench.Rds + pr/main-benchmark-results/*.bench.Rds - name: Comment on PR From 372fa46dcb6b8eb43c60034c7735b1222a021635 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:40:40 +0100 Subject: [PATCH 21/41] units --- benchmark/_compare_results.R | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 04a605917..6c0c2946e 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -23,18 +23,18 @@ for (pr_file in pr_files) { # Get the data frames of timings - pr_df <- as.data.frame(pr_results) - cf_df <- as.data.frame(pr_replicate) - main_df <- as.data.frame(main_results) + pr_df <- pr_results + cf_df <- pr_replicate + main_df <- main_results # Prepare a report report <- list() # Iterate over each function benchmarked for (fn_name in unique(as.character(pr_df$expr))) { - pr_times <- as.numeric( pr_df[["time"]][[1]]) - cf_times <- as.numeric( cf_df[["time"]][[1]]) - main_times <- as.numeric(main_df[["time"]][[1]]) + pr_times <- as.numeric( pr_df[["time"]][[1]] * 1e9) + cf_times <- as.numeric( cf_df[["time"]][[1]] * 1e9) + main_times <- as.numeric(main_df[["time"]][[1]] * 1e9) matched <- if (length(main_times)) { TRUE } else { @@ -96,9 +96,9 @@ for (pr_file in pr_files) { "| `", fn_name, "` | ", status, " | ", bold, round(res$change, 2), "%", bold, "
(p: ", format.pval(res$p_value), ") | ", - signif(res$median_main * 1e3, 3), " \u2192
", - signif(res$median_pr * 1e3, 3), ", ", - signif(res$median_cf * 1e3, 3), " |\n" + signif(res$median_main * 1e-6, 3), " \u2192
", + signif(res$median_pr * 1e-6, 3), ", ", + signif(res$median_cf * 1e-6, 3), " |\n" ) } @@ -112,8 +112,6 @@ for (pr_file in pr_files) { cat(paste0(output, "\nEOF"), file = Sys.getenv("GITHUB_OUTPUT"), append = TRUE) -message(readLines(Sys.getenv("GITHUB_OUTPUT"))) - # Fail the build if there is a significant regression if (any(regressions)) { stop("Significant performance regression detected.") From 3b6fdffeaa38ab6312b8d32a20fba74522668d5a Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:50:02 +0100 Subject: [PATCH 22/41] pr_results w-d --- .github/workflows/benchmark.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 9a79dff07..1e0104f22 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -115,7 +115,6 @@ jobs: - name: Upload main benchmark results if: failure() uses: actions/upload-artifact@v4 - working-directory: pr with: name: main-benchmark-results path: | From 253b32221fab3dfb851cddd42a50cfca28a474d3 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 12:09:31 +0100 Subject: [PATCH 23/41] ws --- .github/workflows/benchmark.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 99760e25d..f6b179745 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -117,7 +117,6 @@ jobs: with: name: main-benchmark-results path: pr/main-benchmark-results/*.bench.Rds - - name: Comment on PR if: always() && github.event_name == 'pull_request' && steps.compare_results.outputs.report != '' From dc6069aabcf37718fcfd35f78c3074e1012440b7 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 13:00:08 +0100 Subject: [PATCH 24/41] run benchmarks from pr directory (!) --- .github/workflows/benchmark.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f6b179745..62706034b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -75,6 +75,7 @@ jobs: # Kill any R processes and clear R temp files pkill -f 'R --' || true rm -rf /tmp/R* || true + cd ../pr Rscript benchmark/_run_benchmarks.R mkdir ../pr/main-benchmark-results/ mv *.bench.Rds ../pr/main-benchmark-results/ From 27edeeed248e3609d51efb852ec7cce56e37f65a Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 13:10:47 +0100 Subject: [PATCH 25/41] now in pr --- .github/workflows/benchmark.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 62706034b..ada5626da 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -77,8 +77,8 @@ jobs: rm -rf /tmp/R* || true cd ../pr Rscript benchmark/_run_benchmarks.R - mkdir ../pr/main-benchmark-results/ - mv *.bench.Rds ../pr/main-benchmark-results/ + mkdir ./main-benchmark-results/ + mv *.bench.Rds ./main-benchmark-results/ shell: bash - name: Benchmark PR From 2d9832af6c3a64a4fe7ccd9d611f4d48dd5e53d3 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:13:58 +0100 Subject: [PATCH 26/41] adapt to bench::mark --- .gitignore | 1 + benchmark/_compare_results.R | 46 ++++++++++++------------------- benchmark/_init.R | 17 ++++++++---- benchmark/bench-Consensus.R | 10 ++++--- benchmark/bench-DropTip.R | 8 +++--- benchmark/bench-SplitList.R | 8 +++--- benchmark/bench-renumber_tree.h.R | 30 ++++++++++---------- 7 files changed, 59 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 508c5af84..9b645c9ec 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ vignettes/*_cache* *.site *.bak /*-benchmark-results +*.bench.Rds diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 6c0c2946e..65bf8c8a9 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -16,25 +16,20 @@ for (pr_file in pr_files) { if (!file.exists(main_file)) next; # Load the results - pr_results <- readRDS(pr_file) - pr_replicate <- if (file.exists(replicate_file)) readRDS(replicate_file) else - pr_results - main_results <- readRDS(main_file) - - - # Get the data frames of timings - pr_df <- pr_results - cf_df <- pr_replicate - main_df <- main_results + rep_exists <- file.exists(replicate_file) + pr1 <- readRDS(pr_file) + pr2 <- if (rep_exists) readRDS(replicate_file) else pr_results + main <- readRDS(main_file) # Prepare a report report <- list() # Iterate over each function benchmarked - for (fn_name in unique(as.character(pr_df$expr))) { - pr_times <- as.numeric( pr_df[["time"]][[1]] * 1e9) - cf_times <- as.numeric( cf_df[["time"]][[1]] * 1e9) - main_times <- as.numeric(main_df[["time"]][[1]] * 1e9) + for (fn_name in unique(as.character(unlist(pr_df[["expression"]])))) { + pr1_times <- as.numeric(pr1[["time"]][[1]] * 1e9) + pr2_times <- as.numeric(pr2[["time"]][[1]] * 1e9) + pr_times <- if (rep_exists) c(pr1_times, pr2_times) else pr1_times + main_times <- as.numeric(main[["time"]][[1]] * 1e9) matched <- if (length(main_times)) { TRUE } else { @@ -42,30 +37,23 @@ for (pr_file in pr_files) { FALSE } - better_result <- t.test(pr_times, main_times, alternative = "less") - worse_result <- t.test(pr_times, main_times, alternative = "greater") - - # The p-value tells us if the PR's performance is significantly slower - # A small p-value (e.g., < 0.05) suggests it is. median_pr <- median(pr_times) - median_cf <- median(cf_times) median_main <- median(main_times) percentage_change <- ((median_main - median_pr) / median_main) * 100 - delta <- abs(median_pr - median_main) - df_delta <- abs(median_pr - median_cf) - noise <- delta < (df_delta * 2) + q <- 0.25 + main_iqr <- quantile(main_times, c(q, 1 - q)) - is_faster <- better_result$p.value < 0.01 && !noise && matched - is_slower <- worse_result$p.value < 0.01 && !noise && matched + is_faster <- median_pr < main_iqr[[1]] && matched + is_slower <- median_pr > main_iqr[[2]] && matched report[[fn_name]] <- list( matched = matched, slower = is_slower, faster = is_faster, p_value = worse_result$p.value, - median_pr = median_pr, - median_cf = median_cf, + median_pr = median_pr1, + median_cf = median_pr2, median_main = median_main, change = percentage_change ) @@ -94,8 +82,7 @@ for (pr_file in pr_files) { message <- paste0( "| `", fn_name, "` | ", status, " | ", - bold, round(res$change, 2), "%", bold, "
(p: ", - format.pval(res$p_value), ") | ", + bold, round(res$change, 2), "%", bold, " | ", signif(res$median_main * 1e-6, 3), " \u2192
", signif(res$median_pr * 1e-6, 3), ", ", signif(res$median_cf * 1e-6, 3), " |\n" @@ -116,3 +103,4 @@ cat(paste0(output, "\nEOF"), file = Sys.getenv("GITHUB_OUTPUT"), append = TRUE) if (any(regressions)) { stop("Significant performance regression detected.") } + diff --git a/benchmark/_init.R b/benchmark/_init.R index a1075685d..1f9b64a25 100644 --- a/benchmark/_init.R +++ b/benchmark/_init.R @@ -1,12 +1,19 @@ library("TreeTools") -ub <- bench::mark -set.seed(1337) - -Benchmark <- function(id, result) { +Benchmark <- function(..., min_iterations = NULL) { + result <- bench::mark(..., min_iterations = min_iterations %||% 3) if (interactive()) { print(result) } else { - saveRDS(result, paste0(id, ".bench.Rds")) + fileroot <- gsub("[\\(\\)]", "_", as.character(result$expression)) + .FileName <- function(fileRoot, i) { + paste0(c(fileroot, i, "bench.Rds"), collapse = ".") + } + i <- double(0) + while(file.exists(.FileName(fileroot, i))) { + if (length(i) == 0) i <- 0 + i <- 1 + i + } + saveRDS(result, .FileName(fileroot, i)) } } diff --git a/benchmark/bench-Consensus.R b/benchmark/bench-Consensus.R index 9a60eec6e..5db5222a2 100644 --- a/benchmark/bench-Consensus.R +++ b/benchmark/bench-Consensus.R @@ -1,12 +1,14 @@ source("benchmark/_init.R") -# library("TreeDist") forest1 <- as.phylo(0:200, 80) forest2 <- as.phylo(0:20, 260) forest3 <- as.phylo(0:1000, 888) -Benchmark("consensus1", ub(Consensus(forest1))) -Benchmark("consensus2", ub(Consensus(forest2))) -Benchmark("consensus3", ub(Consensus(forest3))) +Benchmark(Consensus(forest1)) +Benchmark(Consensus(forest2)) +Benchmark(Consensus(forest3)) + + +# library("TreeDist") # Benchmark("RF1", ub(RobinsonFoulds(forest1), times = 42)) # Benchmark("RF2", ub(RobinsonFoulds(forest2), times = 250)) diff --git a/benchmark/bench-DropTip.R b/benchmark/bench-DropTip.R index ec51dc86f..b4550ecb3 100644 --- a/benchmark/bench-DropTip.R +++ b/benchmark/bench-DropTip.R @@ -11,12 +11,12 @@ if (!file.exists("benchmark/tr80.rds")) { tr80 <- readRDS("benchmark/tr80.rds") tr2000 <- readRDS("benchmark/tr2000.rds") -Benchmark("DropTip.80", ub(replicate(50, DropTip(tr80, 5)))) -Benchmark("DropTip.2000", ub(DropTip(tr2000, 5))) +Benchmark(replicate(50, DropTip(tr80, 5))) +Benchmark(DropTip(tr2000, 5)) unlen80 <- tr80 unlen80$edge.length <- NULL unlen2k <- tr2000 unlen2k$edge.length <- NULL -Benchmark("DropTipUnlen", ub(DropTip(unlen80, 5))) -Benchmark("DropTipUnlen2k", ub(DropTip(unlen2k, 5))) +Benchmark(DropTip(unlen80, 5)) +Benchmark(DropTip(unlen2k, 5)) diff --git a/benchmark/bench-SplitList.R b/benchmark/bench-SplitList.R index 8eb6e9c2a..3e3d04304 100644 --- a/benchmark/bench-SplitList.R +++ b/benchmark/bench-SplitList.R @@ -3,11 +3,11 @@ source("benchmark/_init.R") bigTrees <- as.phylo(1:10, 64*32) someTrees <- as.phylo(1:240, 64*3) -Benchmark("as.Splits192", ub(as.Splits(someTrees))) # 16.5 -Benchmark("as.Splits64.32", ub(as.Splits(bigTrees))) # 23.4 +Benchmark(as.Splits(someTrees)) +Benchmark(as.Splits(bigTrees)) bigSplits <- as.Splits(bigTrees) someSplits <- as.Splits(someTrees) -Benchmark("as.phylo.some", ub(lapply(someSplits, as.phylo))) # 16.7 -Benchmark("as.phylo.big", ub(lapply(bigSplits, as.phylo))) # 34.0 +Benchmark(lapply(someSplits, as.phylo)) +Benchmark(lapply(bigSplits, as.phylo)) diff --git a/benchmark/bench-renumber_tree.h.R b/benchmark/bench-renumber_tree.h.R index 406ef38ff..97f345bda 100644 --- a/benchmark/bench-renumber_tree.h.R +++ b/benchmark/bench-renumber_tree.h.R @@ -21,35 +21,35 @@ NodeNumber <- function(edge) { pec40 <- BalancedTree(40)$edge dpec40 <- Disorder(pec40) npec40 <- NodeNumber(dpec40) -Benchmark("postorder-pec40", ub(TreeTools:::postorder_order(pec40))) -Benchmark("postorder-dpec40", ub(TreeTools:::postorder_order(dpec40))) -Benchmark("postorder-npec40", ub(TreeTools:::postorder_order(npec40))) +Benchmark(TreeTools:::postorder_order(pec40)) +Benchmark(TreeTools:::postorder_order(dpec40)) +Benchmark(TreeTools:::postorder_order(npec40)) pec40k <- BalancedTree(40000)$edge dpec40k <- Disorder(pec40k) npec40k <- NodeNumber(dpec40k) -Benchmark("postorder-pec40k", ub(TreeTools:::postorder_order(pec40k))) -Benchmark("postorder-dpec40k", ub(TreeTools:::postorder_order(dpec40k))) -Benchmark("postorder-npec40k", ub(TreeTools:::postorder_order(npec40k))) +Benchmark(TreeTools:::postorder_order(pec40k)) +Benchmark(TreeTools:::postorder_order(dpec40k)) +Benchmark(TreeTools:::postorder_order(npec40k)) pec40 <- PectinateTree(40)$edge dpec40 <- Disorder(pec40) npec40 <- NodeNumber(dpec40) -Benchmark("postorder-pec40", ub(TreeTools:::postorder_order(pec40))) -Benchmark("postorder-dpec40", ub(TreeTools:::postorder_order(dpec40))) -Benchmark("postorder-npec40", ub(TreeTools:::postorder_order(npec40))) +Benchmark(TreeTools:::postorder_order(pec40)) +Benchmark(TreeTools:::postorder_order(dpec40)) +Benchmark(TreeTools:::postorder_order(npec40)) pec40k <- PectinateTree(40000)$edge dpec40k <- Disorder(pec40k) npec40k <- NodeNumber(dpec40k) -Benchmark("postorder-pec40k", ub(TreeTools:::postorder_order(pec40k))) -Benchmark("postorder-dpec40k", ub(TreeTools:::postorder_order(dpec40k))) -Benchmark("postorder-npec40k", ub(TreeTools:::postorder_order(npec40k))) +Benchmark(TreeTools:::postorder_order(pec40k)) +Benchmark(TreeTools:::postorder_order(dpec40k)) +Benchmark(TreeTools:::postorder_order(npec40k)) r80 <- RandomTree(80, root = TRUE) rnd80 <- r80$edge drnd80 <- Disorder(rnd80) nrnd80 <- NodeNumber(drnd80) -Benchmark("postorder-rnd80", ub(TreeTools:::postorder_order(rnd80))) -Benchmark("postorder-drnd80", ub(TreeTools:::postorder_order(drnd80))) -Benchmark("postorder-nrnd80", ub(TreeTools:::postorder_order(nrnd80))) +Benchmark(TreeTools:::postorder_order(rnd80)) +Benchmark(TreeTools:::postorder_order(drnd80)) +Benchmark(TreeTools:::postorder_order(nrnd80)) From 958e7f913dc6a97ccc6c8fee6bedf4393ab88714 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:15:25 +0100 Subject: [PATCH 27/41] =?UTF-8?q?:=E2=86=92=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmark/_init.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/_init.R b/benchmark/_init.R index 1f9b64a25..7f0819259 100644 --- a/benchmark/_init.R +++ b/benchmark/_init.R @@ -5,7 +5,7 @@ Benchmark <- function(..., min_iterations = NULL) { if (interactive()) { print(result) } else { - fileroot <- gsub("[\\(\\)]", "_", as.character(result$expression)) + fileroot <- gsub("[\\(\\):]", "_", as.character(result$expression)) .FileName <- function(fileRoot, i) { paste0(c(fileroot, i, "bench.Rds"), collapse = ".") } From e9c14640a4fb1dccf1194726c86f6886d01dec14 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:31:32 +0100 Subject: [PATCH 28/41] Use template --- .github/workflows/benchmark.yml | 50 ++++++++++----------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index ada5626da..51bb5e771 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -56,42 +56,22 @@ jobs: needs: benchmark - name: Benchmark PR - working-directory: pr - run: | - R CMD INSTALL . --library=$(mktemp -d) --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - rm -rf /tmp/R* || true - rm -rf /tmp/R_sess* || true - echo 'Start benchmark run' - Rscript benchmark/_run_benchmarks.R - echo 'Benchmark run complete' - mkdir ./pr-benchmark-results/ - mv *.bench.Rds ./pr-benchmark-results/ - shell: bash - - - name: Benchmark main - working-directory: main - run: | - R CMD INSTALL . --library=$(mktemp -d) --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - # Kill any R processes and clear R temp files - pkill -f 'R --' || true - rm -rf /tmp/R* || true - cd ../pr - Rscript benchmark/_run_benchmarks.R - mkdir ./main-benchmark-results/ - mv *.bench.Rds ./main-benchmark-results/ - shell: bash + uses: ms609/actions/benchmark-step + with: + path: pr + output: pr - - name: Benchmark PR - working-directory: pr - run: | - R CMD INSTALL . --library=$(mktemp -d) --preclean --clean --byte-compile --no-docs --no-help --no-test-load --no-multiarch - # Kill any R processes and clear R temp files - pkill -f 'R --' || true - rm -rf /tmp/R* || true - Rscript benchmark/_run_benchmarks.R - mkdir ./pr2-benchmark-results/ - mv *.bench.Rds ./pr2-benchmark-results/ - shell: bash + - name: Benchmark target + uses: ms609/actions/benchmark-step + with: + path: main + output: main + + - name: Benchmark PR again + uses: ms609/actions/benchmark-step + with: + path: pr + output: pr2 - run: dir pr-benchmark-results working-directory: pr From 6d5d019a9b7e6f3dda2a2d95996f4b3289aa21be Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:32:55 +0100 Subject: [PATCH 29/41] sp --- benchmark/_compare_results.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 65bf8c8a9..b73e06c94 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -25,7 +25,7 @@ for (pr_file in pr_files) { report <- list() # Iterate over each function benchmarked - for (fn_name in unique(as.character(unlist(pr_df[["expression"]])))) { + for (fn_name in unique(as.character(unlist(pr1[["expression"]])))) { pr1_times <- as.numeric(pr1[["time"]][[1]] * 1e9) pr2_times <- as.numeric(pr2[["time"]][[1]] * 1e9) pr_times <- if (rep_exists) c(pr1_times, pr2_times) else pr1_times @@ -33,7 +33,7 @@ for (pr_file in pr_files) { matched <- if (length(main_times)) { TRUE } else { - main_times <- main_df[, "time"] + main_times <- main[, "time"] FALSE } From fc72bb94514164006c22289f85e9ad044adbbe2c Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:34:11 +0100 Subject: [PATCH 30/41] sp --- benchmark/_compare_results.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index b73e06c94..ce8954ef8 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -18,7 +18,7 @@ for (pr_file in pr_files) { # Load the results rep_exists <- file.exists(replicate_file) pr1 <- readRDS(pr_file) - pr2 <- if (rep_exists) readRDS(replicate_file) else pr_results + pr2 <- if (rep_exists) readRDS(replicate_file) else pr1 main <- readRDS(main_file) # Prepare a report From 1cb8bd2bd5b95f3174ec634b01eb3d9589009f83 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:35:21 +0100 Subject: [PATCH 31/41] -worse_result --- benchmark/_compare_results.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index ce8954ef8..53d3b837b 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -51,9 +51,8 @@ for (pr_file in pr_files) { matched = matched, slower = is_slower, faster = is_faster, - p_value = worse_result$p.value, - median_pr = median_pr1, - median_cf = median_pr2, + median_pr = median(pr1_times), + median_cf = median(pr2_times), median_main = median_main, change = percentage_change ) From 5067dec8c7d4e1e9d473beb0fdf885dc18d51bde Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:42:30 +0100 Subject: [PATCH 32/41] =?UTF-8?q?Store=20time=20in=20=C2=B5s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmark/_compare_results.R | 14 +++++++------- benchmark/_init.R | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 53d3b837b..ca56727eb 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -26,10 +26,10 @@ for (pr_file in pr_files) { # Iterate over each function benchmarked for (fn_name in unique(as.character(unlist(pr1[["expression"]])))) { - pr1_times <- as.numeric(pr1[["time"]][[1]] * 1e9) - pr2_times <- as.numeric(pr2[["time"]][[1]] * 1e9) + pr1_times <- as.numeric(pr1[["time"]][[1]]) + pr2_times <- as.numeric(pr2[["time"]][[1]]) pr_times <- if (rep_exists) c(pr1_times, pr2_times) else pr1_times - main_times <- as.numeric(main[["time"]][[1]] * 1e9) + main_times <- as.numeric(main[["time"]][[1]]) matched <- if (length(main_times)) { TRUE } else { @@ -41,7 +41,7 @@ for (pr_file in pr_files) { median_main <- median(main_times) percentage_change <- ((median_main - median_pr) / median_main) * 100 - q <- 0.25 + q <- 0.2 main_iqr <- quantile(main_times, c(q, 1 - q)) is_faster <- median_pr < main_iqr[[1]] && matched @@ -82,9 +82,9 @@ for (pr_file in pr_files) { message <- paste0( "| `", fn_name, "` | ", status, " | ", bold, round(res$change, 2), "%", bold, " | ", - signif(res$median_main * 1e-6, 3), " \u2192
", - signif(res$median_pr * 1e-6, 3), ", ", - signif(res$median_cf * 1e-6, 3), " |\n" + signif(res$median_main * 1e-3, 3), " \u2192
", + signif(res$median_pr * 1e-3, 3), ", ", + signif(res$median_cf * 1e-3, 3), " |\n" ) } diff --git a/benchmark/_init.R b/benchmark/_init.R index 7f0819259..e29d4bb85 100644 --- a/benchmark/_init.R +++ b/benchmark/_init.R @@ -1,7 +1,8 @@ library("TreeTools") Benchmark <- function(..., min_iterations = NULL) { - result <- bench::mark(..., min_iterations = min_iterations %||% 3) + result <- bench::mark(..., min_iterations = min_iterations %||% 3, + time_unit = "us") if (interactive()) { print(result) } else { From 72d278ca9aea8bd51054043ee7af2ccc0f7beb84 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:42:58 +0100 Subject: [PATCH 33/41] @main --- .github/workflows/benchmark.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 51bb5e771..7124c9a6b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -56,19 +56,19 @@ jobs: needs: benchmark - name: Benchmark PR - uses: ms609/actions/benchmark-step + uses: ms609/actions/benchmark-step@main with: path: pr output: pr - name: Benchmark target - uses: ms609/actions/benchmark-step + uses: ms609/actions/benchmark-step@main with: path: main output: main - name: Benchmark PR again - uses: ms609/actions/benchmark-step + uses: ms609/actions/benchmark-step@main with: path: pr output: pr2 From 9cbae08b80cbd5cdc133f8e957207c253145fcdd Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:58:22 +0100 Subject: [PATCH 34/41] replacement --- benchmark/_init.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/_init.R b/benchmark/_init.R index e29d4bb85..138bc2aef 100644 --- a/benchmark/_init.R +++ b/benchmark/_init.R @@ -6,7 +6,7 @@ Benchmark <- function(..., min_iterations = NULL) { if (interactive()) { print(result) } else { - fileroot <- gsub("[\\(\\):]", "_", as.character(result$expression)) + fileroot <- gsub("[\\(\\):, /]", "_", as.character(result$expression)) .FileName <- function(fileRoot, i) { paste0(c(fileroot, i, "bench.Rds"), collapse = ".") } From 1a9285e5167ad17bc56380b18383631aff7fedab Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:12:15 +0100 Subject: [PATCH 35/41] Improve comparison --- benchmark/_compare_results.R | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index ca56727eb..c3d9972e4 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -43,9 +43,17 @@ for (pr_file in pr_files) { q <- 0.2 main_iqr <- quantile(main_times, c(q, 1 - q)) + noise_magnitude <- max(abs(c( + median(pr1_times) - median(pr2_times), + main_iqr - median(main_times)))) + - is_faster <- median_pr < main_iqr[[1]] && matched - is_slower <- median_pr > main_iqr[[2]] && matched + is_faster <- matched && + median_pr < main_iqr[[1]] && + median_pr < median_main - noise_magnitude + is_slower <- matched && + median_pr > main_iqr[[2]] && + median_pr > median_main + noise_magnitude report[[fn_name]] <- list( matched = matched, @@ -66,10 +74,7 @@ for (pr_file in pr_files) { status <- ifelse(res$matched, ifelse(res$slower, "\U1F7E0 Slower \U1F641", ifelse(res$faster, "\U1F7E2 Faster!", - ifelse(res$p_value < 0.05, - "\U1F7E1 ?Slower", - "\U26AA NSD") - ) + "\U26AA NSD") ), "\U1F7E4 ?Mismatch") From a659e6a92cabe1bfa5f1cc8e96d77badb0ea28eb Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:12:54 +0100 Subject: [PATCH 36/41] *1e6 --- benchmark/_compare_results.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index c3d9972e4..b17b97c00 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -87,9 +87,9 @@ for (pr_file in pr_files) { message <- paste0( "| `", fn_name, "` | ", status, " | ", bold, round(res$change, 2), "%", bold, " | ", - signif(res$median_main * 1e-3, 3), " \u2192
", - signif(res$median_pr * 1e-3, 3), ", ", - signif(res$median_cf * 1e-3, 3), " |\n" + signif(res$median_main * 1e3, 3), " \u2192
", + signif(res$median_pr * 1e3, 3), ", ", + signif(res$median_cf * 1e3, 3), " |\n" ) } From 2671e2290a1a29d1244f1d0588dcebad3b259daa Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:15:06 +0100 Subject: [PATCH 37/41] percentage threshold --- benchmark/_compare_results.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index b17b97c00..92d9383a7 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -46,12 +46,16 @@ for (pr_file in pr_files) { noise_magnitude <- max(abs(c( median(pr1_times) - median(pr2_times), main_iqr - median(main_times)))) - + + threshold_percent <- 3 is_faster <- matched && + abs(percentage_change) > threshold_percent && median_pr < main_iqr[[1]] && median_pr < median_main - noise_magnitude + is_slower <- matched && + abs(percentage_change) > threshold_percent && median_pr > main_iqr[[2]] && median_pr > median_main + noise_magnitude From 95bf8a2eba3e979d685dc0142142b3d41fbb4daa Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:17:28 +0100 Subject: [PATCH 38/41] New category 'trivial' --- benchmark/_compare_results.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 92d9383a7..a5d3b58e4 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -50,12 +50,10 @@ for (pr_file in pr_files) { threshold_percent <- 3 is_faster <- matched && - abs(percentage_change) > threshold_percent && median_pr < main_iqr[[1]] && median_pr < median_main - noise_magnitude is_slower <- matched && - abs(percentage_change) > threshold_percent && median_pr > main_iqr[[2]] && median_pr > median_main + noise_magnitude @@ -76,10 +74,12 @@ for (pr_file in pr_files) { for (fn_name in names(report)) { res <- report[[fn_name]] status <- ifelse(res$matched, - ifelse(res$slower, "\U1F7E0 Slower \U1F641", - ifelse(res$faster, "\U1F7E2 Faster!", - "\U26AA NSD") - ), + ifelse(abs(percentage_change) > threshold_percent, + ifelse(res$slower, "\U1F7E0 Slower \U1F641", + ifelse(res$faster, "\U1F7E2 Faster!", + "\U26AA NSD"), + ), + "\U1F7E3 Trivial") "\U1F7E4 ?Mismatch") if (res$slower) { From 709fd5733aa446552057f505ba1c484c418f0f5c Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:18:46 +0100 Subject: [PATCH 39/41] Wording --- benchmark/_compare_results.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index a5d3b58e4..174fda818 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -79,7 +79,7 @@ for (pr_file in pr_files) { ifelse(res$faster, "\U1F7E2 Faster!", "\U26AA NSD"), ), - "\U1F7E3 Trivial") + "\U1F7E3 ~Unchanged"), "\U1F7E4 ?Mismatch") if (res$slower) { From 9a248638958b3fcf3a3b31f00174c6a6fa77ecbb Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:21:49 +0100 Subject: [PATCH 40/41] brace order --- .Rbuildignore | 2 +- benchmark/_compare_results.R | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index 4abb721ae..e29dfb730 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -20,8 +20,8 @@ vignettes/.RData ^\.covrignore$ ^\.?R+prof.*$ ^\.zenodo\.json$ +^.*benchmark-results/$ /^benchmark/ -/^*.benchmark-results/ /^src\-/ ^CODE_OF_CONDUCT\.md$ ^codemeta\.json$ diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 174fda818..4e2bf4482 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -73,14 +73,18 @@ for (pr_file in pr_files) { for (fn_name in names(report)) { res <- report[[fn_name]] - status <- ifelse(res$matched, - ifelse(abs(percentage_change) > threshold_percent, - ifelse(res$slower, "\U1F7E0 Slower \U1F641", - ifelse(res$faster, "\U1F7E2 Faster!", - "\U26AA NSD"), - ), - "\U1F7E3 ~Unchanged"), - "\U1F7E4 ?Mismatch") + status <- ifelse( + res$matched, + ifelse( + abs(percentage_change) > threshold_percent, + ifelse( + res$slower, + "\U1F7E0 Slower \U1F641", + ifelse(res$faster, "\U1F7E2 Faster!", "\U26AA NSD") + ), + "\U1F7E3 ~Unchanged" + ), + "\U1F7E4 ?Mismatch") if (res$slower) { has_significant_regression <- TRUE From 31a3a87660ccbcc8d71685f42571f2e5347a74fa Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:23:36 +0100 Subject: [PATCH 41/41] Clean ifel --- benchmark/_compare_results.R | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/benchmark/_compare_results.R b/benchmark/_compare_results.R index 4e2bf4482..6a5fd5f6f 100644 --- a/benchmark/_compare_results.R +++ b/benchmark/_compare_results.R @@ -47,7 +47,7 @@ for (pr_file in pr_files) { median(pr1_times) - median(pr2_times), main_iqr - median(main_times)))) - threshold_percent <- 3 + threshold_percent <- 10 / 3 is_faster <- matched && median_pr < main_iqr[[1]] && @@ -73,18 +73,21 @@ for (pr_file in pr_files) { for (fn_name in names(report)) { res <- report[[fn_name]] - status <- ifelse( - res$matched, - ifelse( - abs(percentage_change) > threshold_percent, - ifelse( - res$slower, - "\U1F7E0 Slower \U1F641", - ifelse(res$faster, "\U1F7E2 Faster!", "\U26AA NSD") - ), + status <- if (res$matched) { + if (abs(percentage_change) > threshold_percent) { + if (res$slower) { + "\U1F7E0 Slower \U1F641" + } else if (res$faster) { + "\U1F7E2 Faster!" + } else { + "\U26AA NSD" + } + } else { "\U1F7E3 ~Unchanged" - ), - "\U1F7E4 ?Mismatch") + } + } else { + "\U1F7E4 ?Mismatch" + } if (res$slower) { has_significant_regression <- TRUE