Skip to content

Benchmark: tak.create profiling — deps.get dominates (92-99%) #1

Description

@harmon25

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 1TAK_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 2bench/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 1TAK_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 2bench/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)

  1. 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.
  2. Skip deps.get when lock unchanged: mix deps.get --check-locked fast-path; only run when mix.lock differs.
  3. Make mise trust best-effort async (already ~20ms, low priority).
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions