When programming with a coding agent, I sometimes want to improve performance. Investigating a slow-looking piece of code can mean tracing how many times it runs as the input grows and how deeply loops are nested.

An LLM can find these places by reading the code. Still, I wondered whether scanning many files for the same kinds of structures is particularly well suited to an LLM. It would be useful to first collect candidates mechanically, then investigate their context and possible improvements with the agent.

So I built nayuta, a lightweight static analysis CLI for Rust code.

Using loops as leads

Nayuta currently examines Rust syntax trees and reports a single loop as an O(N) candidate and nested loops as O(N^m) candidates. It reports the file and line number, so I can go read the code from there.

cargo run -- path/to/project

Without an argument, it recursively scans the current directory, excluding target and .git. With --fail, as in cargo run -- --fail src, it returns exit code 1 when it finds a candidate. The README has usage details and example output.

This is not a proof of complexity. For now, nayuta assumes loops depend on input size, so fixed-iteration loops are reported too. Iterator chains, recursion, and complexity across function calls are not yet handled. For example, if a function called inside a loop scans the entire input, nayuta cannot determine the combined complexity. A missing report does not mean there is no problem, and a report does not mean the code is slow.

After finding a candidate, I still need to investigate how often the code runs, how large the input is, and whether a change preserves the result. I would like to leave that work to a coding agent that can read the surrounding code and run measurements and tests.

Sampling memory accesses

Nayuta also has a memory access measurement feature using Linux perf mem. It requires a supported CPU and kernel; to show source lines, build the target with debug information.

nayuta memory --output run.data -- ./target/release/example argument
nayuta memory --input run.data

The results are samples, not exact read and write counts, and the percentages are not cache miss rates. Sampling can be biased depending on the CPU, so the numbers need to be interpreted accordingly. I want to use the static analysis candidates and runtime observations as leads for deciding where to investigate further.