Summary
Added gated profiling (--profile / TAK_PROFILE=1) to tak.create and benchmarked against:
- small:
tak itself (no LiveViews, ~50 files)
- large: synthetic Phoenix demo (
mix phx.new + 17 large LiveViews × ~245 LOC, 40 handle_event/each + 6 Ecto schemas + migrations) generated via bench/generate_demo.exs
Result: deps.get is 92-99% of wall time even with --no-db. git worktree add is 1-4%, everything else <1%.
How to reproduce
# profiling (zero overhead when off)
mix tak.create feature/foo --profile --no-db
TAK_PROFILE=1 mix tak.create feature/foo --no-db
# large demo
mix archive.install hex phx_new 1.8.12 --force
mix run bench/generate_demo.exs /tmp/tak_phoenix_bench
# bench/generate_demo commits the demo so `git worktree add` works
TAK_PROFILE=1 mix tak.create bench/large armstrong --no-db # run inside demo
# or automated:
mix run bench/profile_demo.exs /tmp/tak_phoenix_bench
Harness: lib/tak/profiling.ex, lib/tak/worktrees.ex:do_create_profiled/3 (per-stage System.monotonic_time), lib/mix/tasks/tak.create.ex: --profile. Tests: test/support/phoenix_demo_generator.ex + test/tak/profiling_test.exs (15 LiveViews, mocked TestSystem sleeps).
Environment
tak branch perf/profiling+fix (based on 255fca4 + profiling)
- Elixir
1.20.2 / OTP 28 / Linux 6.18.33.2-microsoft-standard-WSL2 x86_64 (4 cores)
phx_new 1.8.12
mise present (mise trust measured)
- Postgres not required for these runs (
--no-db)
Raw results
1) Small project (harmon25/tak, --no-db)
Run 1 – TAK_PROFILE=1 mix tak.create bench/small-no-db armstrong --no-db (cold, time):
[TAK PROFILE] breakdown (total 3220ms):
resolve_name 0ms (0.0%)
validate_not_exists 0ms (0.0%)
branch_exists? 12ms (0.4%)
port_check 4ms (0.1%)
mkdir_trees 0ms (0.0%)
git worktree add 59ms (1.8%)
copy_env 2ms (0.1%)
write_dev_local 16ms (0.5%)
mise_config 15ms (0.5%)
deps.get 3100ms (96.3%)
metadata_write 12ms (0.4%)
total 3220ms
real 0m4.758s user 0m0.961s sys 0m0.349s
Run 2 – bench/small-no-db2 hickey --no-db:
[TAK PROFILE] breakdown (total 2831ms):
branch_exists? 15ms (0.5%)
port_check 15ms (0.5%)
git worktree add 107ms (3.8%)
write_dev_local 22ms (0.8%)
mise_config 27ms (1.0%)
deps.get 2629ms (92.9%)
total 2831ms
real 0m4.855s
Even on a tiny repo, deps.get (which does fetch + compile) dominates. Early ad-hoc runs with warm _build were ~1.3-1.5s total, still ~95% deps.get.
2) Large Phoenix demo (/tmp/tak_phoenix_bench_issue, --no-db)
Demo: 17 LiveViews (4165 total LOC, each Live has 25 assigns, 40 HEEx rows, 40 handle_event), 6 schemas (lib/tak_bench_demo/*.ex with 12 fields + indexes), 6 migrations, router with 17 live routes, :tak wired as path: "/home/doug/projects/tak".
mix deps.get in demo root (cold, fetching Hex): real 0m30.008s (warm real 0m2.174s)
mix deps.get is the same command tak.create runs inside the worktree (with cd: trees/<name>).
Run 1 – TAK_PROFILE=1 mix tak.create bench/large-no-db armstrong --no-db (inside demo, committed 0f43205):
[TAK PROFILE] breakdown (total 20424ms):
branch_exists? 18ms (0.1%)
port_check 4ms (0.0%)
git worktree add 58ms (0.3%)
write_dev_local 4ms (0.0%)
mise_config 21ms (0.1%)
deps.get 20313ms (99.5%)
total 20424ms
Run 2 – bench/large-no-db2 hickey --no-db:
[TAK PROFILE] breakdown (total 23127ms):
branch_exists? 11ms (0.0%)
git worktree add 51ms (0.2%)
mise_config 22ms (0.1%)
deps.get 23015ms (99.5%)
total 23127ms
Large demo is ~6-7× slower than small with --no-db, essentially all in deps.get compiling Phoenix + deps + 17 LiveViews. Git/mise/file IO are noise.
mix deps.get --prefix inside demo (warm) is only 2s, so the 20-23s inside worktree is compilation of deps + app (cold _build).
Interpretation
deps.get ≈ wall time. Non-profile overhead (resolve, validate, port, mkdir, copy_env, dev.local, metadata) is <30ms combined.
git worktree add 50-110ms (1-4%) – not the bottleneck; will stay low unless hooks/LFS introduced.
mise trust 15-27ms – negligible.
ecto.setup not measured here (--no-db). Expected to add further seconds on top of deps.get when --db is used (creates DB + runs 6 migrations + seeds + compile). Previous ad-hoc --no-db vs default suggests ecto.setup would be additive, not overlapping.
- Cold
_build per worktree is the root cause. Each worktree gets a fresh _build/deps checkout; mix deps.get recompiles all deps + app.
Suggested next optimizations (not implemented)
- Share or link
_build/deps: symlink deps and/or _build from main checkout, or mix deps.get --check-locked + copy, or rsync with --link-dest. Needs correctness for different Elixir/OTP and prod vs dev.
- Skip
deps.get when lock unchanged: mix deps.get --check-locked fast-path; only run when mix.lock differs.
- Make
mise trust best-effort async (already ~20ms, low priority).
- Split
ecto.setup timing (follow-up benchmark with --db + Postgres) and consider ecto.create + migrate without seeds as default.
Artifacts
- Branch:
perf/profiling+fix
- Scripts:
bench/generate_demo.exs (wires :tak, commits demo), bench/profile_demo.exs (warm/cleanup loop), bench/README.md
- Tests:
test/support/phoenix_demo_generator.ex + test/tak/profiling_test.exs
- Docs:
mix help tak.create now lists --profile
Recorded from harmon25/tak fork (issues disabled there) — filing upstream for visibility. Raw logs from bench/BENCHMARK.md if persisted.
Summary
Added gated profiling (
--profile/TAK_PROFILE=1) totak.createand benchmarked against:takitself (no LiveViews, ~50 files)mix phx.new+ 17 large LiveViews × ~245 LOC, 40handle_event/each + 6 Ecto schemas + migrations) generated viabench/generate_demo.exsResult:
deps.getis 92-99% of wall time even with--no-db.git worktree addis 1-4%, everything else <1%.How to reproduce
Harness:
lib/tak/profiling.ex,lib/tak/worktrees.ex:do_create_profiled/3(per-stageSystem.monotonic_time),lib/mix/tasks/tak.create.ex: --profile. Tests:test/support/phoenix_demo_generator.ex+test/tak/profiling_test.exs(15 LiveViews, mockedTestSystemsleeps).Environment
takbranchperf/profiling+fix(based on255fca4+ profiling)1.20.2/ OTP28/ Linux6.18.33.2-microsoft-standard-WSL2x86_64 (4 cores)phx_new1.8.12misepresent (mise trustmeasured)--no-db)Raw results
1) Small project (
harmon25/tak,--no-db)Run 1 –
TAK_PROFILE=1 mix tak.create bench/small-no-db armstrong --no-db(cold,time):Run 2 –
bench/small-no-db2 hickey --no-db:2) Large Phoenix demo (
/tmp/tak_phoenix_bench_issue,--no-db)Demo: 17 LiveViews (
4165total LOC, eachLivehas 25 assigns, 40 HEEx rows, 40handle_event), 6 schemas (lib/tak_bench_demo/*.exwith 12 fields + indexes), 6 migrations, router with 17liveroutes,:takwired aspath: "/home/doug/projects/tak".mix deps.getin demo root (cold, fetching Hex):real 0m30.008s(warmreal 0m2.174s)mix deps.getis the same commandtak.createruns inside the worktree (withcd: trees/<name>).Run 1 –
TAK_PROFILE=1 mix tak.create bench/large-no-db armstrong --no-db(inside demo, committed0f43205):Run 2 –
bench/large-no-db2 hickey --no-db:mix deps.get --prefixinside demo (warm) is only2s, so the 20-23s inside worktree is compilation of deps + app (cold_build).Interpretation
deps.get≈ wall time. Non-profile overhead (resolve, validate, port, mkdir, copy_env, dev.local, metadata) is <30ms combined.git worktree add50-110ms (1-4%) – not the bottleneck; will stay low unless hooks/LFS introduced.mise trust15-27ms – negligible.ecto.setupnot measured here (--no-db). Expected to add further seconds on top ofdeps.getwhen--dbis used (creates DB + runs 6 migrations + seeds + compile). Previous ad-hoc--no-dbvs default suggestsecto.setupwould be additive, not overlapping._buildper worktree is the root cause. Each worktree gets a fresh_build/depscheckout;mix deps.getrecompiles all deps + app.Suggested next optimizations (not implemented)
_build/deps: symlinkdepsand/or_buildfrom main checkout, ormix deps.get --check-locked+ copy, orrsyncwith--link-dest. Needs correctness for different Elixir/OTP and prod vs dev.deps.getwhen lock unchanged:mix deps.get --check-lockedfast-path; only run whenmix.lockdiffers.mise trustbest-effort async (already ~20ms, low priority).ecto.setuptiming (follow-up benchmark with--db+ Postgres) and considerecto.create+migratewithout seeds as default.Artifacts
perf/profiling+fixbench/generate_demo.exs(wires:tak, commits demo),bench/profile_demo.exs(warm/cleanup loop),bench/README.mdtest/support/phoenix_demo_generator.ex+test/tak/profiling_test.exsmix help tak.createnow lists--profileRecorded from
harmon25/takfork (issues disabled there) — filing upstream for visibility. Raw logs frombench/BENCHMARK.mdif persisted.