Investigate lines, memory, and third-party tools¶
Function-level evidence has narrowed the search to normalization, but it cannot distinguish the lines inside that function. This episode adds line-level evidence and builds a practical tool-selection map for CPU, call-path, and memory questions.
Objectives
Measure individual lines with
line_profiler.Explain when visualization, sampling, or memory profiling is useful.
Select a profiler based on the question and execution environment.
Instructor note
10 min teaching
15 min type-along
5 min tool-selection exercise
Type-along: line profiling¶
line_profiler needs an explicit target. Open line_profile_wordcount.py; its core is:
from pathlib import Path
import sys
code_dir = next(
path.resolve()
for path in (Path("content/episodes/code"), Path("../content/episodes/code"))
if path.is_dir()
)
if str(code_dir) not in sys.path:
sys.path.insert(0, str(code_dir))
from line_profiler import LineProfiler
from wordcount import count_words, make_workload, normalize_slow
profiler = LineProfiler()
profiler.add_function(normalize_slow)
profiled_count = profiler(count_words)
profiled_count(make_workload(2_000))
profiler.print_stats()
Run it with:
$ python line_profile_wordcount.py
The report includes hits, total time, time per hit, and percentage for each measured line. Focus on lines that combine many hits with appreciable time. The programmatic API makes the selected function explicit and works without a temporary source edit. The installed kernprof command is a convenient alternative for scripts that use the @profile decorator.
Do not read the percentage column in isolation. A cheap line executed many times and an expensive line executed once can both deserve attention for different reasons. Connect hits, time per hit, and total contribution back to the algorithm before proposing a change.
In IPython, load the extension and profile an imported function:
%load_ext line_profiler
%lprun -f normalize_slow count_words(text)
Line profilers perturb execution and can magnify call overhead. They are best used after function profiling has reduced the search area.
Memory-oriented magics¶
CPU profiles cannot explain peak memory or allocation growth. Some existing IPython workflows use the separately installed memory_profiler extension:
%load_ext memory_profiler
%memit count_words(text)
%mprun -f count_words count_words(text)
%memit measures the memory change around a statement, while %mprun attributes memory use to lines of a function defined in an importable file. These magics are included for tool awareness and are not part of the core environment. For new work, compare their suitability and maintenance status with a CPU-and-memory profiler such as Scalene and with the tools supported by your HPC site.
Choose a tool for the question¶
Question |
Starting tool |
Trade-off |
|---|---|---|
Did the change improve elapsed time? |
|
Comparison only; no location data |
Which call path is expensive? |
|
Deterministic call overhead |
Which source line dominates? |
|
Instrument only selected functions |
How do callers relate visually? |
Requires a profile file and usually a browser |
|
Where do CPU and memory grow in a longer live process? |
|
Optional dependencies; platform, maintenance, and policy must be checked |
What happens inside native code or across the whole node? |
System profiler supplied by the HPC site |
Tool, permissions, and interpretation are platform-specific |
To view the saved profile locally, run snakeviz wordcount-slow.prof. On a remote node, do not expose a listening port without following site policy; download the non-sensitive profile file or use documented SSH forwarding. Function names, paths, and workload structure in a profile can themselves be sensitive.
Sampling profilers interrupt periodically instead of recording every call. They tend to perturb long-running applications less but can miss brief events. Memory profilers answer allocation and peak-memory questions, which CPU timings cannot. Start with the least intrusive tool that answers the current question.
On an HPC system, also ask where the tool runs. A browser-based viewer may belong on a login or visualization node while collection happens in a compute allocation. A whole-node profiler may require site modules or permissions. Separating collection from inspection makes the workflow more portable and reduces the temptation to expose an ad-hoc service from a compute node.
Exercise
Choose a first tool for each situation: (1) a two-hour batch job spends time in an unknown phase; (2) one known Python function allocates too much memory; (3) two implementations need a fair elapsed-time comparison.
Solution
(1) Begin with a low-overhead sampling or system profiler permitted by the site. (2) Use a memory-aware profiler such as Scalene on a representative reduced case. (3) Use a repeated benchmark with timeit or an application-level perf_counter boundary. In every case, preserve correctness and record the environment.
Keypoints
Move from coarse to fine measurements; instrument only where needed.
CPU time, elapsed time, call paths, lines, and memory are different questions.
Profiles may expose paths and workload structure, so handle them as potentially sensitive artifacts.
See also
See the documentation for line_profiler, SnakeViz, and Scalene.