Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions BENCHMARK.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,30 +53,30 @@ callers, not a list of lines.

| repo | symbol | grep lines | grep tok | ctx tok | ratio | callers | ctx says |
|---|---|---|---|---|---|---|---|
| ripgrep | `new` | 1216 | 80967 | 17594 | 4.6x | 359 | name collision |
| ripgrep | `unwrap` | 1147 | 75965 | 411 | 184.8x | 5 | name collision |
| ripgrep | `arg` | 357 | 22479 | 159 | 141.4x | 2 | **complete** |
| ripgrep | `parse_low_raw` | 565 | 38372 | 108 | 355.3x | 0 | incomplete |
| requests | `get` | 180 | 11532 | 4266 | 2.7x | 89 | name collision |
| requests | `httpbin` | 156 | 10327 | 100 | 103.3x | 0 | incomplete |
| requests | `prepare` | 59 | 3625 | 2008 | 1.8x | 36 | ambiguous |
| requests | `send` | 50 | 2891 | 1206 | 2.4x | 20 | name collision |
| got | `get` | 1308 | 76569 | 285 | 268.7x | 2 | name collision |
| got | `destroy` | 189 | 10171 | 1484 | 6.9x | 33 | incomplete |
| got | `setHeader` | 159 | 9922 | 1544 | 6.4x | 33 | **complete** |
| got | `json` | 141 | 8566 | 996 | 8.6x | 23 | ambiguous |
| cobra | `executeCommand` | 307 | 20238 | 7408 | 2.7x | 193 | **complete** |
| cobra | `Flags` | 205 | 13060 | 3444 | 3.8x | 90 | **complete** |
| cobra | `AddCommand` | 158 | 8915 | 5448 | 1.6x | 135 | **complete** |
| cobra | `Name` | 119 | 7550 | 2365 | 3.2x | 65 | **complete** |
| chi | `Get` | 260 | 16267 | 3382 | 4.8x | 92 | incomplete |
| chi | `Write` | 222 | 12324 | 385 | 32.0x | 4 | incomplete |
| chi | `HandlerFunc` | 169 | 11430 | 153 | 74.7x | 2 | name collision |
| chi | `testRequest` | 154 | 10670 | 1468 | 7.3x | 40 | ambiguous |
| preact | `render` | 58 | 3161 | 272 | 11.6x | 2 | incomplete |
| preact | `createElement` | 21 | 1296 | 142 | 9.1x | 0 | incomplete |
| preact | `createSignal` | 11 | 718 | 252 | 2.8x | 3 | ambiguous |
| preact | `unstable_runWithPriority` | 7 | 454 | 223 | 2.0x | 1 | incomplete |
| ripgrep | `new` | 1216 | 35975 | 17594 | 2.0x | 359 | name collision |
| ripgrep | `unwrap` | 1147 | 33526 | 411 | 81.6x | 5 | name collision |
| ripgrep | `arg` | 357 | 9270 | 159 | 58.3x | 2 | **complete** |
| ripgrep | `parse_low_raw` | 565 | 17467 | 108 | 161.7x | 0 | incomplete |
| requests | `get` | 180 | 4812 | 4266 | 1.1x | 89 | name collision |
| requests | `httpbin` | 156 | 4503 | 100 | 45.0x | 0 | incomplete |
| requests | `prepare` | 59 | 1423 | 2008 | 0.7x | 36 | ambiguous |
| requests | `send` | 50 | 1024 | 1206 | 0.8x | 20 | name collision |
| got | `get` | 1308 | 29917 | 285 | 105.0x | 2 | name collision |
| got | `destroy` | 189 | 3430 | 1484 | 2.3x | 33 | incomplete |
| got | `setHeader` | 159 | 4251 | 1544 | 2.8x | 33 | **complete** |
| got | `json` | 141 | 3537 | 996 | 3.6x | 23 | ambiguous |
| cobra | `executeCommand` | 307 | 9084 | 7408 | 1.2x | 193 | **complete** |
| cobra | `Flags` | 205 | 5612 | 3444 | 1.6x | 90 | **complete** |
| cobra | `AddCommand` | 158 | 3174 | 5448 | 0.6x | 135 | **complete** |
| cobra | `Name` | 119 | 3227 | 2365 | 1.4x | 65 | **complete** |
| chi | `Get` | 260 | 6994 | 3382 | 2.1x | 92 | incomplete |
| chi | `Write` | 222 | 4406 | 385 | 11.4x | 4 | incomplete |
| chi | `HandlerFunc` | 169 | 5402 | 153 | 35.3x | 2 | name collision |
| chi | `testRequest` | 154 | 5177 | 1468 | 3.5x | 40 | ambiguous |
| preact | `render` | 58 | 1034 | 272 | 3.8x | 2 | incomplete |
| preact | `createElement` | 21 | 526 | 142 | 3.7x | 0 | incomplete |
| preact | `createSignal` | 11 | 315 | 252 | 1.2x | 3 | ambiguous |
| preact | `unstable_runWithPriority` | 7 | 197 | 223 | 0.9x | 1 | incomplete |

**Read the last column before the ratio column.** A large ratio on an
`incomplete` row is not a win — it is ctx answering cheaply *because it could not
Expand Down Expand Up @@ -122,7 +122,7 @@ can hide a large fraction of the third quantity. That is a limitation this
harness found on a third-party repository, which is the argument for running it
on third-party repositories.

Over the 6 `complete` answers above, the median is **3.5x** fewer tokens than the fair grep.
Over the 6 `complete` answers above, the median is **1.5x** fewer tokens than the fair grep.

The median, not the best case or the range. Individual ratios here span two orders of
magnitude, and the largest of them are the least trustworthy — a ratio is only as good
Expand All @@ -132,4 +132,4 @@ where ctx saw least. Read the table, not this line.
---

Generated by `./bench/run.sh` from `bench/repos.tsv` (6 repositories).
ctx version: `ctx 0.23.0`.
ctx version: `ctx 0.24.0`.
54 changes: 26 additions & 28 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tree they were run against, so a version string dates them without making them
current — this file had drifted a whole minor version before anyone noticed.
Regenerate it in the release commit instead.

The repo under analysis: **15 Rust files, 4 Markdown files, 19 modules, 6,293
The repo under analysis: **15 Rust files, 4 Markdown files, 19 modules, 6,338
call sites.** Small enough to read in a sitting, which makes it a fair place to
check whether the answers are actually right.

Expand Down Expand Up @@ -82,19 +82,19 @@ $ ctx trace build_graph --depth 2
~ heuristic edge (verify) · * one branch of a dispatch fan-out

extract::build_graph [src/extract/mod.rs:226] [+1 outside graph]
├─ extract::disambiguate_module_names [src/extract/mod.rs:373]
├─ extract::disambiguate_module_names [src/extract/mod.rs:372]
├─ extract::go::extract [src/extract/go.rs:9] [+1 outside graph]
│ ├─ extract::go::function [src/extract/go.rs:669] (depth limit)
│ ├─ extract::go::imports [src/extract/go.rs:238] (depth limit)
│ ├─ extract::go::interfaces_in [src/extract/go.rs:168] (depth limit)
│ ├─ model::content_hash [src/model.rs:178]
│ ├─ model::content_hash [src/model.rs:185]
│ ├─ extract::go::package_level_item [src/extract/go.rs:197] (depth limit)
│ ├─ extract::go::receiver_of_method [src/extract/go.rs:499] (depth limit)
│ ├─ extract::go::returns_in [src/extract/go.rs:115] (depth limit)
│ ├─ extract::go::types [src/extract/go.rs:320] (depth limit)
│ └─ extract::go::values [src/extract/go.rs:468] (depth limit)
├─ extract::go_modules [src/extract/mod.rs:927]
│ ├─ extract::go_segs [src/extract/mod.rs:915]
├─ extract::go_modules [src/extract/mod.rs:940]
│ ├─ extract::go_segs [src/extract/mod.rs:928]
│ └─ extract::walker [src/extract/mod.rs:109] (depth limit)
├─ extract::markdown::extract [src/extract/markdown.rs:45] [+1 outside graph]
│ ├─ extract::markdown::assign_lines [src/extract/markdown.rs:139] (depth limit)
Expand All @@ -111,27 +111,25 @@ extract::build_graph [src/extract/mod.rs:226] [+1 outside graph]
│ ├─ extract::markdown::setext_heading [src/extract/markdown.rs:296]
│ ├─ extract::markdown::slug [src/extract/markdown.rs:13]
│ └─ extract::markdown::strip_inline [src/extract/markdown.rs:460]
├─ model::Lang::sep [src/model.rs:165]
├─ extract::module_name [src/extract/mod.rs:608]
│ ├─ model::Lang::sep [src/model.rs:165]
│ └─ extract::safe_seg [src/extract/mod.rs:604] (depth limit)
├─ extract::nearest_go_module [src/extract/mod.rs:972]
├─ extract::module_name [src/extract/mod.rs:604]
│ └─ model::Lang::sep [src/model.rs:172]
├─ extract::nearest_go_module [src/extract/mod.rs:985]
├─ extract::needs_jsx [src/extract/mod.rs:222]
├─ extract::python::extract [src/extract/python.rs:9] [+1 outside graph]
│ ├─ extract::python::module_level_item [src/extract/python.rs:35] (depth limit)
│ └─ extract::python::visit [src/extract/python.rs:73] (depth limit)
├─ extract::resolve_deps [src/extract/mod.rs:683]
│ ├─ extract::apply_calls [src/extract/mod.rs:2473] (depth limit)
│ ├─ extract::build_universe [src/extract/mod.rs:1493] (depth limit)
│ ├─ extract::compute_calls [src/extract/mod.rs:2310] (depth limit)
│ ├─ extract::display_reexport [src/extract/mod.rs:2480] (depth limit)
│ ├─ extract::go_mod_index [src/extract/mod.rs:989]
│ ├─ extract::go_packages [src/extract/mod.rs:1033] (depth limit)
│ ├─ model::Module::resolve_segs~ [src/model.rs:319] (depth limit)
│ └─ extract::resolve_from [src/extract/mod.rs:1163] (depth limit)
├─ extract::resolve_deps [src/extract/mod.rs:679]
│ ├─ extract::apply_calls [src/extract/mod.rs:2486] (depth limit)
│ ├─ extract::build_universe [src/extract/mod.rs:1506] (depth limit)
│ ├─ extract::compute_calls [src/extract/mod.rs:2323] (depth limit)
│ ├─ extract::display_reexport [src/extract/mod.rs:2493] (depth limit)
│ ├─ extract::go_mod_index [src/extract/mod.rs:1002]
│ ├─ extract::go_packages [src/extract/mod.rs:1046] (depth limit)
│ ├─ model::Module::resolve_segs~ [src/model.rs:326] (depth limit)
│ └─ extract::resolve_from [src/extract/mod.rs:1176] (depth limit)
├─ extract::rust::extract [src/extract/rust.rs:9] [+1 outside graph]
│ └─ extract::rust::visit [src/extract/rust.rs:25] (depth limit)
├─ extract::slash_path [src/extract/mod.rs:578]
├─ extract::slash_path [src/extract/mod.rs:574]
├─ extract::source_files [src/extract/mod.rs:142]
│ ├─ extract::lang_selected [src/extract/mod.rs:131] (depth limit)
│ └─ extract::walker [src/extract/mod.rs:109] (depth limit)
Expand Down Expand Up @@ -270,9 +268,9 @@ under this root. Calls into std or a third-party crate are excluded, because no
internal edge could exist for them however good the resolver gets.

## Every call site, bucketed
call sites: 6293
call sites: 6338
internal edges: 1355 [17 heuristic (~), 0 dispatch fan-out (*)]
external (provable): 4871 (77.4%) callee defined nowhere here — std/extern
external (provable): 4916 (77.6%) callee defined nowhere here — std/extern
unresolved internal: 67 (1.1%) the real misses — see below

## What ctx missed (callee names that exist here but went unpinned)
Expand Down Expand Up @@ -313,7 +311,7 @@ grep these; every other edge in the map is one ctx could prove.
95.3% recall comes with **the exact grep list for the other 4.7%** — nine
names, with counts and the modules they live in.

The denominator is honest too. 4,871 of 6,293 call sites go into `std` or a
The denominator is honest too. 4,916 of 6,338 call sites go into `std` or a
third-party crate, where no internal edge could ever exist, so they're excluded
rather than quietly inflating the percentage. That classification is by evidence
— *is this name defined anywhere under the root?* — not a hardcoded list.
Expand All @@ -338,7 +336,7 @@ Ranked by dependency centrality (PageRank); higher = more depended-upon.

score in out module
0.2954 13 0 model [19 items]
0.1525 11 1 extract [100 items]
0.1525 11 1 extract [103 items]
0.0601 4 2 render [13 items]
0.0468 3 3 view [11 items]
0.0448 2 4 query [141 items]
Expand All @@ -365,7 +363,7 @@ Jump-to-def without knowing the file, across languages.
$ ctx def Universe
1 definition(s) of 'Universe':

extract::Universe [struct] src/extract/mod.rs:1474-1491 #85eadbaee90b
extract::Universe [struct] src/extract/mod.rs:1487-1504 #85eadbaee90b
struct Universe { methods: MethodIndex, all_names: BTreeSet<String>, module_segs: BTreeSet<String>, implementors: HashMap<String, BTreeSet<(String, String)>>, fields: HashMap<String, BTreeMap<String, String>> } — Whole-tree symbol evidence, built once and shared by every module's
```

Expand Down Expand Up @@ -415,7 +413,7 @@ $ ctx context Receiver --include-source
# Context: model::Receiver

## Definition
model::Receiver [enum] src/model.rs:266-295 #eaedf052414f
model::Receiver [enum] src/model.rs:273-302 #eaedf052414f
pub enum Receiver { Free | SelfType | SelfField | Typed | Dyn | Returned | Unknown } — How a callee was referenced — governs how confidently a receiver method

```rust
Expand Down Expand Up @@ -452,7 +450,7 @@ pub enum Receiver {
```

## Referenced by — dependents (14 signature(s))
- extract (src/extract/mod.rs:1916)
- extract (src/extract/mod.rs:1929)
fn field_receiver(ty: &str, uni: &Universe) -> Receiver
- extract::go (src/extract/go.rs:520)
struct TypeEnv { vars: HashMap<String, Receiver> }
Expand All @@ -478,7 +476,7 @@ pub enum Receiver {
fn let_binding(n: Node, src: &str, env: &TypeEnv) -> Option<(String, Receiver)>
- extract::rust (src/extract/rust.rs:567)
fn receiver_of(v: Node, src: &str, env: &TypeEnv) -> Receiver
- model (src/model.rs:301)
- model (src/model.rs:308)
pub struct RawCall { pub path: String, pub recv: Receiver, pub line: usize, pub end_line: usize }
Signature references only: uses inside function BODIES are not indexed.

Expand Down
18 changes: 13 additions & 5 deletions bench/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,21 @@ tokens_of () { local n; n=$(wc -c); echo $(( n / 3 )); }
# The grep a competent agent would actually write: call syntax, language
# filtered, skipping the directories ctx skips. Not a naive `grep -rn name`,
# because beating a strawman proves nothing.
# Searched from INSIDE the tree, against `.`, so hits read `./src/x.rs:12:` and
# not `/wherever/the/cache/lives/src/x.rs:12:`. The cost of an answer is counted
# in bytes, so an absolute path would bill grep for the length of a directory
# name that has nothing to do with either tool — and would make the figure
# depend on where the corpus happened to be cloned. Measured on ripgrep, the
# prefix alone was 9,322 tokens, 21% of that row. It also biased the comparison
# in ctx's favour, since ctx already reports paths relative to its root.
fair_grep () {
local dir="$1" sym="$2"
grep -rnE "\\b${sym}\\(" "$dir" \
--include='*.rs' --include='*.py' --include='*.ts' --include='*.tsx' --include='*.go' \
--exclude-dir=.git --exclude-dir=node_modules --exclude-dir=target \
--exclude-dir=__pycache__ --exclude-dir=.venv --exclude-dir=venv \
--exclude-dir=dist --exclude-dir=build 2>/dev/null || true
( cd "$dir" 2>/dev/null || return 0
grep -rnE "\\b${sym}\\(" . \
--include='*.rs' --include='*.py' --include='*.ts' --include='*.tsx' --include='*.go' \
--exclude-dir=.git --exclude-dir=node_modules --exclude-dir=target \
--exclude-dir=__pycache__ --exclude-dir=.venv --exclude-dir=venv \
--exclude-dir=dist --exclude-dir=build 2>/dev/null || true )
}

# Fetch one pinned commit, shallow. Cached, because re-cloning on every run
Expand Down
Loading
Loading