\n",
"
Quiz
\n",
"
\n",
"
Donald E. Knuth said: \"Premature optimization...\"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
"
\n",
" \n",
" \n",
"
\n",
" \n",
" \n",
"
\n",
" \n",
" \n",
"
\n",
" \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
" "
],
"text/plain": [
""
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"quiz('Donald E. Knuth said: \"Premature optimization...\"',\n",
" [\n",
" \"... is the root of all evil\",\n",
" \"... requires lots of experience\",\n",
" \"... should be left to assembly programmers\",\n",
" \"... is the reason why TeX is so fast\",\n",
" ], 'len(\"METAFONT\") - len(\"TeX\") - len(\"CWEB\")')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"This quote should always remind us that after a good design, you should always _first_ measure and _then_ optimize."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Building a Profiler\n",
"\n",
"Having discussed profilers from a _user_ perspective, let us now dive into how they are actually implemented. It turns out we can use most of our existing infrastructure to implement a simple tracing profiler with only a few lines of code."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The program we will apply our profiler on is – surprise! – our ongoing example, `remove_html_markup()`. Our aim is to understand how much time is spent _in each line of the code_ (such that we have a new feature on top of Python `cProfile`)."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.352206Z",
"iopub.status.busy": "2025-10-26T17:54:55.352110Z",
"iopub.status.idle": "2025-10-26T17:54:55.353618Z",
"shell.execute_reply": "2025-10-26T17:54:55.353314Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Intro_Debugging import remove_html_markup"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.354923Z",
"iopub.status.busy": "2025-10-26T17:54:55.354844Z",
"iopub.status.idle": "2025-10-26T17:54:55.356344Z",
"shell.execute_reply": "2025-10-26T17:54:55.356124Z"
},
"slideshow": {
"slide_type": "fragment"
},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# ignore\n",
"from typing import Any, Optional, Type, Dict, Tuple, List"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.357620Z",
"iopub.status.busy": "2025-10-26T17:54:55.357536Z",
"iopub.status.idle": "2025-10-26T17:54:55.358886Z",
"shell.execute_reply": "2025-10-26T17:54:55.358619Z"
},
"slideshow": {
"slide_type": "fragment"
},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# ignore\n",
"from bookutils import print_content"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.360081Z",
"iopub.status.busy": "2025-10-26T17:54:55.360006Z",
"iopub.status.idle": "2025-10-26T17:54:55.361346Z",
"shell.execute_reply": "2025-10-26T17:54:55.361144Z"
},
"slideshow": {
"slide_type": "subslide"
},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# ignore\n",
"import inspect"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.362762Z",
"iopub.status.busy": "2025-10-26T17:54:55.362660Z",
"iopub.status.idle": "2025-10-26T17:54:55.481753Z",
"shell.execute_reply": "2025-10-26T17:54:55.481497Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"238 \u001b[34mdef\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mremove_html_markup\u001b[39;49;00m(s): \u001b[37m# type: ignore\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"239 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"240 quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"241 out = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"242 \u001b[37m\u001b[39;49;00m\n",
"243 \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n",
"244 \u001b[34massert\u001b[39;49;00m tag \u001b[35mor\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n",
"245 \u001b[37m\u001b[39;49;00m\n",
"246 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n",
"247 tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"248 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n",
"249 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"250 \u001b[34melif\u001b[39;49;00m (c == \u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mor\u001b[39;49;00m c == \u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m) \u001b[35mand\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n",
"251 quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n",
"252 \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n",
"253 out = out + c\u001b[37m\u001b[39;49;00m\n",
"254 \u001b[37m\u001b[39;49;00m\n",
"255 \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"print_content(inspect.getsource(remove_html_markup), '.py',\n",
" start_line_number=238)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We introduce a class `PerformanceTracer` that tracks, for each line in the code:\n",
"\n",
"* how _often_ it was executed (`hits`), and\n",
"* _how much time_ was spent during its execution (`time`).\n",
"\n",
"To this end, we make use of our `Timer` class, which measures time, and the `Tracer` class from [the chapter on tracing](Tracer.ipynb), which allows us to track every line of the program as it is being executed."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.483204Z",
"iopub.status.busy": "2025-10-26T17:54:55.483108Z",
"iopub.status.idle": "2025-10-26T17:54:55.484726Z",
"shell.execute_reply": "2025-10-26T17:54:55.484510Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Tracer import Tracer"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"In `PerformanceTracker`, the attributes `hits` and `time` are mappings indexed by unique locations – that is, pairs of function name and line number."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.486046Z",
"iopub.status.busy": "2025-10-26T17:54:55.485964Z",
"iopub.status.idle": "2025-10-26T17:54:55.487453Z",
"shell.execute_reply": "2025-10-26T17:54:55.487198Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"Location = Tuple[str, int]"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.488858Z",
"iopub.status.busy": "2025-10-26T17:54:55.488773Z",
"iopub.status.idle": "2025-10-26T17:54:55.490843Z",
"shell.execute_reply": "2025-10-26T17:54:55.490568Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class PerformanceTracer(Tracer):\n",
" \"\"\"Trace time and #hits for individual program lines\"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" \"\"\"Constructor.\"\"\"\n",
" super().__init__()\n",
" self.reset_timer()\n",
" self.hits: Dict[Location, int] = {}\n",
" self.time: Dict[Location, float] = {}\n",
"\n",
" def reset_timer(self) -> None:\n",
" self.timer = Timer.Timer()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"As common in this book, we want to use `PerformanceTracer` in a `with`-block around the function call(s) to be tracked:\n",
"\n",
"```python\n",
"with PerformanceTracer() as perf_tracer:\n",
" function(...)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"When entering the `with` block (`__enter__()`), we reset all timers. Also, coming from the `__enter__()` method of the superclass `Tracer`, we enable tracing through the `traceit()` method."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.492466Z",
"iopub.status.busy": "2025-10-26T17:54:55.492366Z",
"iopub.status.idle": "2025-10-26T17:54:55.493992Z",
"shell.execute_reply": "2025-10-26T17:54:55.493705Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from types import FrameType"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.495369Z",
"iopub.status.busy": "2025-10-26T17:54:55.495283Z",
"iopub.status.idle": "2025-10-26T17:54:55.497046Z",
"shell.execute_reply": "2025-10-26T17:54:55.496724Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class PerformanceTracer(PerformanceTracer):\n",
" def __enter__(self) -> Any:\n",
" \"\"\"Enter a `with` block.\"\"\"\n",
" super().__enter__()\n",
" self.reset_timer()\n",
" return self"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The `traceit()` method extracts the current location. It increases the corresponding `hits` value by 1, and adds the elapsed time to the corresponding `time`."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.498523Z",
"iopub.status.busy": "2025-10-26T17:54:55.498424Z",
"iopub.status.idle": "2025-10-26T17:54:55.500770Z",
"shell.execute_reply": "2025-10-26T17:54:55.500466Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class PerformanceTracer(PerformanceTracer):\n",
" def traceit(self, frame: FrameType, event: str, arg: Any) -> None:\n",
" \"\"\"Tracing function; called for every line.\"\"\"\n",
" t = self.timer.elapsed_time()\n",
" location = (frame.f_code.co_name, frame.f_lineno)\n",
"\n",
" self.hits.setdefault(location, 0)\n",
" self.time.setdefault(location, 0.0)\n",
" self.hits[location] += 1\n",
" self.time[location] += t\n",
"\n",
" self.reset_timer()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"This is it already. We can now determine where most time is spent in `remove_html_markup()`. We invoke it 10,000 times such that we can average over runs:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:55.502409Z",
"iopub.status.busy": "2025-10-26T17:54:55.502306Z",
"iopub.status.idle": "2025-10-26T17:54:56.187295Z",
"shell.execute_reply": "2025-10-26T17:54:56.186973Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"with PerformanceTracer() as perf_tracer:\n",
" for i in range(10000):\n",
" s = remove_html_markup('foo')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Here are the hits. For every line executed, we see how often it was executed. The most executed line is the `for` loop with 110,000 hits – once for each of the 10 characters in `foo`, once for the final check, and all of this 10,000 times."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.188960Z",
"iopub.status.busy": "2025-10-26T17:54:56.188871Z",
"iopub.status.idle": "2025-10-26T17:54:56.191109Z",
"shell.execute_reply": "2025-10-26T17:54:56.190869Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('__init__', 17): 1,\n",
" ('__init__', 19): 1,\n",
" ('clock', 8): 1,\n",
" ('clock', 12): 2,\n",
" ('__init__', 20): 2,\n",
" ('remove_html_markup', 238): 10000,\n",
" ('remove_html_markup', 239): 10000,\n",
" ('remove_html_markup', 240): 10000,\n",
" ('remove_html_markup', 241): 10000,\n",
" ('remove_html_markup', 243): 110000,\n",
" ('remove_html_markup', 244): 100000,\n",
" ('remove_html_markup', 246): 100000,\n",
" ('remove_html_markup', 247): 20000,\n",
" ('remove_html_markup', 248): 80000,\n",
" ('remove_html_markup', 250): 60000,\n",
" ('remove_html_markup', 252): 60000,\n",
" ('remove_html_markup', 249): 20000,\n",
" ('remove_html_markup', 253): 30000,\n",
" ('remove_html_markup', 255): 20000}"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"perf_tracer.hits"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The `time` attribute collects how much time was spent in each line. Within the loop, again, the `for` statement takes the most time. The other lines show some variability, though."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.192606Z",
"iopub.status.busy": "2025-10-26T17:54:56.192506Z",
"iopub.status.idle": "2025-10-26T17:54:56.195471Z",
"shell.execute_reply": "2025-10-26T17:54:56.195124Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('__init__', 17): 0.00024116699933074415,\n",
" ('__init__', 19): 1.3329845387488604e-06,\n",
" ('clock', 8): 2.4999899324029684e-06,\n",
" ('clock', 12): 1.7909915186464787e-06,\n",
" ('__init__', 20): 1.334003172814846e-06,\n",
" ('remove_html_markup', 238): 0.007917946233646944,\n",
" ('remove_html_markup', 239): 0.00618785762344487,\n",
" ('remove_html_markup', 240): 0.006357173289870843,\n",
" ('remove_html_markup', 241): 0.006170973560074344,\n",
" ('remove_html_markup', 243): 0.06910131918266416,\n",
" ('remove_html_markup', 244): 0.0627616380515974,\n",
" ('remove_html_markup', 246): 0.06305116802104749,\n",
" ('remove_html_markup', 247): 0.012841210089391097,\n",
" ('remove_html_markup', 248): 0.04974727283115499,\n",
" ('remove_html_markup', 250): 0.03712841047672555,\n",
" ('remove_html_markup', 252): 0.037927715689875185,\n",
" ('remove_html_markup', 249): 0.012593008199473843,\n",
" ('remove_html_markup', 253): 0.01872212707530707,\n",
" ('remove_html_markup', 255): 0.013112093147356063}"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"perf_tracer.time"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"For a full profiler, these numbers would now be sorted and printed in a table, much like `cProfile` does. However, we will borrow some material from previous chapters and annotate our code accordingly."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Visualizing Performance Metrics\n",
"\n",
"In the [chapter on statistical debugging](StatisticalDebugger.ipynb), we have encountered the `CoverageCollector` class, which collects line and function coverage during execution, using a `collect()` method that is invoked for every line. We will repurpose this class to collect arbitrary _metrics_ on the lines executed, notably time taken."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Collecting Time Spent"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.197359Z",
"iopub.status.busy": "2025-10-26T17:54:56.197235Z",
"iopub.status.idle": "2025-10-26T17:54:56.199040Z",
"shell.execute_reply": "2025-10-26T17:54:56.198767Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from StatisticalDebugger import CoverageCollector, SpectrumDebugger"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The `MetricCollector` class is an abstract superclass that provides an interface to access a particular metric."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.200303Z",
"iopub.status.busy": "2025-10-26T17:54:56.200226Z",
"iopub.status.idle": "2025-10-26T17:54:56.202200Z",
"shell.execute_reply": "2025-10-26T17:54:56.201969Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class MetricCollector(CoverageCollector):\n",
" \"\"\"Abstract superclass for collecting line-specific metrics\"\"\"\n",
"\n",
" def metric(self, event: Any) -> Optional[float]:\n",
" \"\"\"Return a metric for an event, or none.\"\"\"\n",
" return None\n",
"\n",
" def all_metrics(self, func: str) -> List[float]:\n",
" \"\"\"Return all metric for a function `func`.\"\"\"\n",
" return []"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Given these metrics, we can also compute sums and maxima for a single function."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.203677Z",
"iopub.status.busy": "2025-10-26T17:54:56.203585Z",
"iopub.status.idle": "2025-10-26T17:54:56.205275Z",
"shell.execute_reply": "2025-10-26T17:54:56.205051Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class MetricCollector(MetricCollector):\n",
" def total(self, func: str) -> float:\n",
" return sum(self.all_metrics(func))\n",
"\n",
" def maximum(self, func: str) -> float:\n",
" return max(self.all_metrics(func))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let us instantiate this superclass into `TimeCollector` – a subclass that measures time. This is modeled after our `PerformanceTracer` class, above; notably, the `time` attribute serves the same role."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.206751Z",
"iopub.status.busy": "2025-10-26T17:54:56.206633Z",
"iopub.status.idle": "2025-10-26T17:54:56.209531Z",
"shell.execute_reply": "2025-10-26T17:54:56.209202Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class TimeCollector(MetricCollector):\n",
" \"\"\"Collect time executed for each line\"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" \"\"\"Constructor\"\"\"\n",
" super().__init__()\n",
" self.reset_timer()\n",
" self.time: Dict[Location, float] = {}\n",
" self.add_items_to_ignore([Timer.Timer, Timer.clock])\n",
"\n",
" def collect(self, frame: FrameType, event: str, arg: Any) -> None:\n",
" \"\"\"Invoked for every line executed. Accumulate time spent.\"\"\"\n",
" t = self.timer.elapsed_time()\n",
" super().collect(frame, event, arg)\n",
" location = (frame.f_code.co_name, frame.f_lineno)\n",
"\n",
" self.time.setdefault(location, 0.0)\n",
" self.time[location] += t\n",
"\n",
" self.reset_timer()\n",
"\n",
" def reset_timer(self) -> None:\n",
" self.timer = Timer.Timer()\n",
"\n",
" def __enter__(self) -> Any:\n",
" super().__enter__()\n",
" self.reset_timer()\n",
" return self"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The `metric()` and `all_metrics()` methods accumulate the metric (time taken) for an individual function:"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.211177Z",
"iopub.status.busy": "2025-10-26T17:54:56.211063Z",
"iopub.status.idle": "2025-10-26T17:54:56.213411Z",
"shell.execute_reply": "2025-10-26T17:54:56.213103Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class TimeCollector(TimeCollector):\n",
" def metric(self, location: Any) -> Optional[float]:\n",
" if location in self.time:\n",
" return self.time[location]\n",
" else:\n",
" return None\n",
"\n",
" def all_metrics(self, func: str) -> List[float]:\n",
" return [time\n",
" for (func_name, lineno), time in self.time.items()\n",
" if func_name == func]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Here's how to use `TimeCollector()` – again, in a `with` block:"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.214948Z",
"iopub.status.busy": "2025-10-26T17:54:56.214836Z",
"iopub.status.idle": "2025-10-26T17:54:56.229243Z",
"shell.execute_reply": "2025-10-26T17:54:56.228957Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"with TimeCollector() as collector:\n",
" for i in range(100):\n",
" s = remove_html_markup('foo')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The `time` attribute holds the time spent in each line:"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.230695Z",
"iopub.status.busy": "2025-10-26T17:54:56.230603Z",
"iopub.status.idle": "2025-10-26T17:54:56.232583Z",
"shell.execute_reply": "2025-10-26T17:54:56.232289Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"('remove_html_markup', 238) 0.00015183707000687718\n",
"('remove_html_markup', 239) 0.00010900790221057832\n",
"('remove_html_markup', 240) 0.00011175306281074882\n",
"('remove_html_markup', 241) 0.00010875475709326565\n",
"('remove_html_markup', 243) 0.001230446039699018\n",
"('remove_html_markup', 244) 0.0011077363742515445\n",
"('remove_html_markup', 246) 0.0011040171957574785\n",
"('remove_html_markup', 247) 0.00022176094353199005\n",
"('remove_html_markup', 248) 0.0008924873836804181\n",
"('remove_html_markup', 250) 0.0006938762671779841\n",
"('remove_html_markup', 252) 0.000667455984512344\n",
"('remove_html_markup', 249) 0.0002219062007497996\n",
"('remove_html_markup', 253) 0.00032781687332317233\n",
"('remove_html_markup', 255) 0.00023653166135773063\n"
]
}
],
"source": [
"for location, time_spent in collector.time.items():\n",
" print(location, time_spent)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"And we can also create a total for an entire function:"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.233978Z",
"iopub.status.busy": "2025-10-26T17:54:56.233884Z",
"iopub.status.idle": "2025-10-26T17:54:56.235940Z",
"shell.execute_reply": "2025-10-26T17:54:56.235690Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0.00718538771616295"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"collector.total('remove_html_markup')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Visualizing Time Spent\n",
"\n",
"Let us now go and visualize these numbers in a simple form. The idea is to assign each line a _color_ whose saturation indicates the time spent in that line relative to the time spent in the function overall – the higher the fraction, the darker the line. We create a `MetricDebugger` class built as a specialization of `SpectrumDebugger`, in which `suspiciousness()` and `color()` are repurposed to show these metrics."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.237568Z",
"iopub.status.busy": "2025-10-26T17:54:56.237458Z",
"iopub.status.idle": "2025-10-26T17:54:56.241019Z",
"shell.execute_reply": "2025-10-26T17:54:56.240723Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class MetricDebugger(SpectrumDebugger):\n",
" \"\"\"Visualize a metric\"\"\"\n",
"\n",
" def metric(self, location: Location) -> float:\n",
" sum = 0.0\n",
" for outcome in self.collectors:\n",
" for collector in self.collectors[outcome]:\n",
" assert isinstance(collector, MetricCollector)\n",
" m = collector.metric(location)\n",
" if m is not None:\n",
" sum += m # type: ignore\n",
"\n",
" return sum\n",
"\n",
" def total(self, func_name: str) -> float:\n",
" total = 0.0\n",
" for outcome in self.collectors:\n",
" for collector in self.collectors[outcome]:\n",
" assert isinstance(collector, MetricCollector)\n",
" total += sum(collector.all_metrics(func_name))\n",
"\n",
" return total\n",
"\n",
" def maximum(self, func_name: str) -> float:\n",
" maximum = 0.0\n",
" for outcome in self.collectors:\n",
" for collector in self.collectors[outcome]:\n",
" assert isinstance(collector, MetricCollector)\n",
" maximum = max(maximum, \n",
" max(collector.all_metrics(func_name)))\n",
"\n",
" return maximum\n",
"\n",
" def suspiciousness(self, location: Location) -> float:\n",
" func_name, _ = location\n",
" return self.metric(location) / self.total(func_name)\n",
"\n",
" def color(self, location: Location) -> str:\n",
" func_name, _ = location\n",
" hue = 240 # blue\n",
" saturation = 100 # fully saturated\n",
" darkness = self.metric(location) / self.maximum(func_name)\n",
" lightness = 100 - darkness * 25\n",
" return f\"hsl({hue}, {saturation}%, {lightness}%)\"\n",
"\n",
" def tooltip(self, location: Location) -> str:\n",
" return f\"{super().tooltip(location)} {self.metric(location)}\""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We can now introduce `PerformanceDebugger` as a subclass of `MetricDebugger`, using an arbitrary `MetricCollector` (such as `TimeCollector`) to obtain the metric we want to visualize."
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.242479Z",
"iopub.status.busy": "2025-10-26T17:54:56.242379Z",
"iopub.status.idle": "2025-10-26T17:54:56.244189Z",
"shell.execute_reply": "2025-10-26T17:54:56.243913Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class PerformanceDebugger(MetricDebugger):\n",
" \"\"\"Collect and visualize a metric\"\"\"\n",
"\n",
" def __init__(self, collector_class: Type, log: bool = False):\n",
" assert issubclass(collector_class, MetricCollector)\n",
" super().__init__(collector_class, log=log)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"With `PerformanceDebugger`, we inherit all the capabilities of `SpectrumDebugger`, such as showing the (relative) percentage of time spent in a table. We see that the `for` condition and the following `assert` take most of the time, followed by the first condition."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.245468Z",
"iopub.status.busy": "2025-10-26T17:54:56.245383Z",
"iopub.status.idle": "2025-10-26T17:54:56.260389Z",
"shell.execute_reply": "2025-10-26T17:54:56.260092Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"with PerformanceDebugger(TimeCollector) as debugger:\n",
" for i in range(100):\n",
" s = remove_html_markup('foo')"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.261773Z",
"iopub.status.busy": "2025-10-26T17:54:56.261680Z",
"iopub.status.idle": "2025-10-26T17:54:56.263902Z",
"shell.execute_reply": "2025-10-26T17:54:56.263638Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 238 1% def remove_html_markup(s): # type: ignore\n",
" 239 1% tag = False\n",
" 240 1% quote = False\n",
" 241 1% out = \"\"\n",
" 242 0%\n",
" 243 16% for c in s:\n",
" 244 15% assert tag or not quote\n",
" 245 0%\n",
" 246 15% if c == '<' and not quote:\n",
" 247 3% tag = True\n",
" 248 12% elif c == '>' and not quote:\n",
" 249 3% tag = False\n",
" 250 9% elif (c == '\"' or c == \"'\") and tag:\n",
" 251 0% quote = not quote\n",
" 252 9% elif not tag:\n",
" 253 4% out = out + c\n",
" 254 0%\n",
" 255 3% return out\n",
"\n"
]
}
],
"source": [
"print(debugger)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"However, we can also visualize these percentages, using shades of blue to indicate those lines most time spent in:"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.265447Z",
"iopub.status.busy": "2025-10-26T17:54:56.265358Z",
"iopub.status.idle": "2025-10-26T17:54:56.268228Z",
"shell.execute_reply": "2025-10-26T17:54:56.267923Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/html": [
" 238 def remove_html_markup(s): # type: ignore
\n",
" 239 tag = False
\n",
" 240 quote = False
\n",
" 241 out = ""
\n",
" 242
\n",
" 243 for c in s:
\n",
" 244 assert tag or not quote
\n",
" 245
\n",
" 246 if c == '<' and not quote:
\n",
" 247 tag = True
\n",
" 248 elif c == '>' and not quote:
\n",
" 249 tag = False
\n",
" 250 elif (c == '"' or c == "'") and tag:
\n",
" 251 quote = not quote
\n",
" 252 elif not tag:
\n",
" 253 out = out + c
\n",
" 254
\n",
" 255 return out
\n"
],
"text/markdown": [
"| `remove_html_markup` | `s='foo'` | \n",
"| ---------------------- | ---- | \n",
"| remove_html_markup:238 | X | \n",
"| remove_html_markup:239 | X | \n",
"| remove_html_markup:240 | X | \n",
"| remove_html_markup:241 | X | \n",
"| remove_html_markup:243 | X | \n",
"| remove_html_markup:244 | X | \n",
"| remove_html_markup:246 | X | \n",
"| remove_html_markup:247 | X | \n",
"| remove_html_markup:248 | X | \n",
"| remove_html_markup:249 | X | \n",
"| remove_html_markup:250 | X | \n",
"| remove_html_markup:252 | X | \n",
"| remove_html_markup:253 | X | \n",
"| remove_html_markup:255 | X | \n"
],
"text/plain": [
" 238 1% def remove_html_markup(s): # type: ignore\n",
" 239 1% tag = False\n",
" 240 1% quote = False\n",
" 241 1% out = \"\"\n",
" 242 0%\n",
" 243 16% for c in s:\n",
" 244 15% assert tag or not quote\n",
" 245 0%\n",
" 246 15% if c == '<' and not quote:\n",
" 247 3% tag = True\n",
" 248 12% elif c == '>' and not quote:\n",
" 249 3% tag = False\n",
" 250 9% elif (c == '\"' or c == \"'\") and tag:\n",
" 251 0% quote = not quote\n",
" 252 9% elif not tag:\n",
" 253 4% out = out + c\n",
" 254 0%\n",
" 255 3% return out"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"debugger"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Other Metrics\n",
"\n",
"Our framework is flexible enough to collect (and visualize) arbitrary metrics. This `HitCollector` class, for instance, collects how often a line is being executed."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.269603Z",
"iopub.status.busy": "2025-10-26T17:54:56.269519Z",
"iopub.status.idle": "2025-10-26T17:54:56.272221Z",
"shell.execute_reply": "2025-10-26T17:54:56.272004Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class HitCollector(MetricCollector):\n",
" \"\"\"Collect how often a line is executed\"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" super().__init__()\n",
" self.hits: Dict[Location, int] = {}\n",
"\n",
" def collect(self, frame: FrameType, event: str, arg: Any) -> None:\n",
" super().collect(frame, event, arg)\n",
" location = (frame.f_code.co_name, frame.f_lineno)\n",
"\n",
" self.hits.setdefault(location, 0)\n",
" self.hits[location] += 1\n",
"\n",
" def metric(self, location: Location) -> Optional[int]:\n",
" if location in self.hits:\n",
" return self.hits[location]\n",
" else:\n",
" return None\n",
"\n",
" def all_metrics(self, func: str) -> List[float]:\n",
" return [hits\n",
" for (func_name, lineno), hits in self.hits.items()\n",
" if func_name == func]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We can plug in this class into `PerformanceDebugger` to obtain a distribution of lines executed:"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.273647Z",
"iopub.status.busy": "2025-10-26T17:54:56.273557Z",
"iopub.status.idle": "2025-10-26T17:54:56.284335Z",
"shell.execute_reply": "2025-10-26T17:54:56.284070Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"with PerformanceDebugger(HitCollector) as debugger:\n",
" for i in range(100):\n",
" s = remove_html_markup('foo')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"In total, during this call to `remove_html_markup()`, there are 6,400 lines executed:"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.285720Z",
"iopub.status.busy": "2025-10-26T17:54:56.285638Z",
"iopub.status.idle": "2025-10-26T17:54:56.287625Z",
"shell.execute_reply": "2025-10-26T17:54:56.287380Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"6400.0"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"debugger.total('remove_html_markup')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Again, we can visualize the distribution as a table and using colors. We can see how the shade gets lighter in the lower part of the loop as individual conditions have been met."
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.289019Z",
"iopub.status.busy": "2025-10-26T17:54:56.288934Z",
"iopub.status.idle": "2025-10-26T17:54:56.290851Z",
"shell.execute_reply": "2025-10-26T17:54:56.290634Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 238 1% def remove_html_markup(s): # type: ignore\n",
" 239 1% tag = False\n",
" 240 1% quote = False\n",
" 241 1% out = \"\"\n",
" 242 0%\n",
" 243 17% for c in s:\n",
" 244 15% assert tag or not quote\n",
" 245 0%\n",
" 246 15% if c == '<' and not quote:\n",
" 247 3% tag = True\n",
" 248 12% elif c == '>' and not quote:\n",
" 249 3% tag = False\n",
" 250 9% elif (c == '\"' or c == \"'\") and tag:\n",
" 251 0% quote = not quote\n",
" 252 9% elif not tag:\n",
" 253 4% out = out + c\n",
" 254 0%\n",
" 255 3% return out\n",
"\n"
]
}
],
"source": [
"print(debugger)"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.292152Z",
"iopub.status.busy": "2025-10-26T17:54:56.292078Z",
"iopub.status.idle": "2025-10-26T17:54:56.294738Z",
"shell.execute_reply": "2025-10-26T17:54:56.294535Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/html": [
" 238 def remove_html_markup(s): # type: ignore
\n",
" 239 tag = False
\n",
" 240 quote = False
\n",
" 241 out = ""
\n",
" 242
\n",
" 243 for c in s:
\n",
" 244 assert tag or not quote
\n",
" 245
\n",
" 246 if c == '<' and not quote:
\n",
" 247 tag = True
\n",
" 248 elif c == '>' and not quote:
\n",
" 249 tag = False
\n",
" 250 elif (c == '"' or c == "'") and tag:
\n",
" 251 quote = not quote
\n",
" 252 elif not tag:
\n",
" 253 out = out + c
\n",
" 254
\n",
" 255 return out
\n"
],
"text/markdown": [
"| `remove_html_markup` | `s='foo'` | \n",
"| ---------------------- | ---- | \n",
"| remove_html_markup:238 | X | \n",
"| remove_html_markup:239 | X | \n",
"| remove_html_markup:240 | X | \n",
"| remove_html_markup:241 | X | \n",
"| remove_html_markup:243 | X | \n",
"| remove_html_markup:244 | X | \n",
"| remove_html_markup:246 | X | \n",
"| remove_html_markup:247 | X | \n",
"| remove_html_markup:248 | X | \n",
"| remove_html_markup:249 | X | \n",
"| remove_html_markup:250 | X | \n",
"| remove_html_markup:252 | X | \n",
"| remove_html_markup:253 | X | \n",
"| remove_html_markup:255 | X | \n"
],
"text/plain": [
" 238 1% def remove_html_markup(s): # type: ignore\n",
" 239 1% tag = False\n",
" 240 1% quote = False\n",
" 241 1% out = \"\"\n",
" 242 0%\n",
" 243 17% for c in s:\n",
" 244 15% assert tag or not quote\n",
" 245 0%\n",
" 246 15% if c == '<' and not quote:\n",
" 247 3% tag = True\n",
" 248 12% elif c == '>' and not quote:\n",
" 249 3% tag = False\n",
" 250 9% elif (c == '\"' or c == \"'\") and tag:\n",
" 251 0% quote = not quote\n",
" 252 9% elif not tag:\n",
" 253 4% out = out + c\n",
" 254 0%\n",
" 255 3% return out"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"debugger"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Integrating with Delta Debugging\n",
"\n",
"Besides identifying causes for performance issues in the code, one may also search for causes in the _input_, using [Delta Debugging](DeltaDebugger.ipynb). This can be useful if one does not immediately want to embark into investigating the code, but maybe first determine external influences that are related to performance issues."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Here is a variant of `remove_html_markup()` that introduces a (rather obvious) performance issue."
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.296099Z",
"iopub.status.busy": "2025-10-26T17:54:56.296023Z",
"iopub.status.idle": "2025-10-26T17:54:56.297601Z",
"shell.execute_reply": "2025-10-26T17:54:56.297290Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import time"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.299014Z",
"iopub.status.busy": "2025-10-26T17:54:56.298929Z",
"iopub.status.idle": "2025-10-26T17:54:56.301056Z",
"shell.execute_reply": "2025-10-26T17:54:56.300851Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"def remove_html_markup_ampersand(s: str) -> str:\n",
" tag = False\n",
" quote = False\n",
" out = \"\"\n",
"\n",
" for c in s:\n",
" assert tag or not quote\n",
"\n",
" if c == '&':\n",
" time.sleep(0.1) # <-- the obvious performance issue\n",
"\n",
" if c == '<' and not quote:\n",
" tag = True\n",
" elif c == '>' and not quote:\n",
" tag = False\n",
" elif (c == '\"' or c == \"'\") and tag:\n",
" quote = not quote\n",
" elif not tag:\n",
" out = out + c\n",
"\n",
" return out"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We can easily trigger this issue by measuring time taken:"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.302278Z",
"iopub.status.busy": "2025-10-26T17:54:56.302199Z",
"iopub.status.idle": "2025-10-26T17:54:56.616068Z",
"shell.execute_reply": "2025-10-26T17:54:56.615370Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0.30978220800170675"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with Timer.Timer() as t:\n",
" remove_html_markup_ampersand('&&&')\n",
"t.elapsed_time()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let us set up a test that checks whether the performance issue is present."
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.620529Z",
"iopub.status.busy": "2025-10-26T17:54:56.620093Z",
"iopub.status.idle": "2025-10-26T17:54:56.624724Z",
"shell.execute_reply": "2025-10-26T17:54:56.624060Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def remove_html_test(s: str) -> None:\n",
" with Timer.Timer() as t:\n",
" remove_html_markup_ampersand(s)\n",
" assert t.elapsed_time() < 0.1"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can now apply delta debugging to determine a minimum input that causes the failure:"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.628938Z",
"iopub.status.busy": "2025-10-26T17:54:56.628640Z",
"iopub.status.idle": "2025-10-26T17:54:56.633413Z",
"shell.execute_reply": "2025-10-26T17:54:56.631667Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"s_fail = 'foo&'"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.636469Z",
"iopub.status.busy": "2025-10-26T17:54:56.636343Z",
"iopub.status.idle": "2025-10-26T17:54:56.744777Z",
"shell.execute_reply": "2025-10-26T17:54:56.743922Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"with DeltaDebugger.DeltaDebugger() as dd:\n",
" remove_html_test(s_fail)"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:56.749542Z",
"iopub.status.busy": "2025-10-26T17:54:56.749243Z",
"iopub.status.idle": "2025-10-26T17:54:57.276206Z",
"shell.execute_reply": "2025-10-26T17:54:57.275124Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'s': '&'}"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dd.min_args()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"For performance issues, however, a minimal input is often not enough to highlight the failure cause. This is because short inputs tend to take less processing time than longer inputs, which increases the risks of a spurious diagnosis. A better alternative is to compute a _maximum_ input where the issue does not occur:"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:57.280133Z",
"iopub.status.busy": "2025-10-26T17:54:57.279865Z",
"iopub.status.idle": "2025-10-26T17:54:57.491265Z",
"shell.execute_reply": "2025-10-26T17:54:57.488806Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"s_pass = dd.max_args()"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:57.495629Z",
"iopub.status.busy": "2025-10-26T17:54:57.495225Z",
"iopub.status.idle": "2025-10-26T17:54:57.501249Z",
"shell.execute_reply": "2025-10-26T17:54:57.500132Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'s': 'fooamp;'}"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"s_pass"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We see that the culprit character (the `&`) is removed. This tells us the failure-inducing difference – or, more precisely, the cause for the performance issue."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Lessons Learned\n",
"\n",
"* To measure performance,\n",
" * instrument the code such that the time taken per function (or line) is collected; or\n",
" * sample the execution that at regular intervals, the active call stack is collected.\n",
"* To make code performant, focus on efficient algorithms, efficient data types, and sufficient abstraction such that you can replace them by alternatives.\n",
"* Beyond efficient algorithms and data types, do _not_ optimize before measuring."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Next Steps\n",
"\n",
"This chapter concludes the part on abstracting failures. The next part will focus on\n",
"\n",
"* [repairing code automatically](Repairer.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Background\n",
"\n",
"[Scalene](https://github.com/plasma-umass/scalene) is a high-performance, high-precision CPU, GPU, and memory profiler for Python. In contrast to the standard Python `cProfile` profiler, it uses _sampling_ instead of instrumentation or relying on Python's tracing facilities; and it also supports line-by-line profiling. Scalene might be the tool of choice if you want to go beyond basic profiling.\n",
"\n",
"The Wikipedia articles on [profiling](https://en.wikipedia.org/wiki/Profiling_(computer_programming)) and [performance analysis tools](https://en.wikipedia.org/wiki/List_of_performance_analysis_tools) provide several additional resources on profiling tools and how to apply them in practice."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Exercises"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Exercise 1: Profiling Memory Usage\n",
"\n",
"The Python [`tracemalloc` module](https://docs.python.org/3/library/tracemalloc.html) allows tracking memory usage during execution. Between `tracemalloc.start()` and `tracemalloc.end()`, use `tracemalloc.get_traced_memory()` to obtain how much memory is currently being consumed:"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:58.551991Z",
"iopub.status.busy": "2025-10-26T17:54:58.551864Z",
"iopub.status.idle": "2025-10-26T17:54:58.555879Z",
"shell.execute_reply": "2025-10-26T17:54:58.555583Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import tracemalloc"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:58.557638Z",
"iopub.status.busy": "2025-10-26T17:54:58.557517Z",
"iopub.status.idle": "2025-10-26T17:54:58.559672Z",
"shell.execute_reply": "2025-10-26T17:54:58.559155Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"tracemalloc.start()"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:58.561453Z",
"iopub.status.busy": "2025-10-26T17:54:58.561246Z",
"iopub.status.idle": "2025-10-26T17:54:58.565113Z",
"shell.execute_reply": "2025-10-26T17:54:58.564706Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"20551"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_size, peak_size = tracemalloc.get_traced_memory()\n",
"current_size"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:54:58.566828Z",
"iopub.status.busy": "2025-10-26T17:54:58.566615Z",
"iopub.status.idle": "2025-10-26T17:54:58.568751Z",
"shell.execute_reply": "2025-10-26T17:54:58.568518Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"tracemalloc.stop()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Create a subclass of `MetricCollector` named `MemoryCollector`. Make it measure the memory consumption before and after each line executed (0 if negative), and visualize the impact of individual lines on memory. Create an appropriate test program that (temporarily) consumes larger amounts of memory."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Exercise 2: Statistical Performance Debugging\n",
"\n",
"In a similar way as we integrated a binary \"performance test\" with delta debugging, we can also integrate such a test with other techniques. Combining a performance test with [Statistical Debugging](StatisticalDebugger.ipynb), for instance, will highlight those lines whose execution correlates with low performance. But then, the performance test need not be binary, as with functional pass/fail tests – you can also _weight_ individual lines by _how much_ they impact performance. Create a variant of `StatisticalDebugger` that reflects the impact of individual lines on an arbitrary (summarized) performance metric."
]
}
],
"metadata": {
"ipub": {
"bibliography": "fuzzingbook.bib",
"toc": true
},
"kernelspec": {
"display_name": "3.12.8",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.4"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"toc-autonumbering": false
},
"nbformat": 4,
"nbformat_minor": 4
}