Seyed Masoud Hosseini · Overview · Study log · Weekly summaries · Ideas · Search · Transcript · RSS feed

Performance Engineering of Software Systems · Lecture 18 of 23 · 1:11:22

Lecture 18: Domain-Specific Languages and Autotuning

18. Domain Specific Languages and Autotuning on YouTube

Study guide

What this lecture covers

Guest lecturer Saman Amarasinghe asks how you get near-hand-optimized performance without forcing every programmer to hand-tune code for every machine and dataset. His answer is domain-specific languages that separate the algorithm (what to compute) from the schedule (how to compute it), illustrated with GraphIt for graph processing and Halide for image processing, plus autotuning frameworks like OpenTuner that search the schedule space automatically.

This is a guest lecture within MIT's performance engineering course, connecting to the course's ongoing project that uses OpenTuner. After watching, you can explain why separating algorithm from schedule helps performance portability, describe the parallelism/locality/redundant-work trade-off that both GraphIt and Halide schedules navigate, and understand why autotuning beats hand-built models or hard-coded heuristics for large search spaces.

Key ideas

  • Domain-specific language (DSL): a language restricted to one problem domain, which makes domain knowledge explicit so both programmers and compilers can exploit it, unlike general-purpose languages or libraries where such structure is hidden.
  • Algorithm vs. schedule: GraphIt and Halide both separate the algorithm (what result to compute) from the schedule (execution order, parallelization, data layout), so changing performance strategy doesn't require rewriting the algorithm.
  • Push vs. pull graph traversal: pushing updates to neighbors gives parallelism but needs atomic updates to avoid races; pulling updates from neighbors avoids races (each node only writes itself) but may do wasted work checking neighbors with nothing new.
  • Graph partitioning trade-off: partitioning a graph improves locality and can add parallelism, but introduces extra bookkeeping and possibly replicated nodes, illustrating that optimizations exist on a trade-off surface rather than being universally best.
  • Parallelism, locality, redundant work: the three axes performance engineers balance across both DSLs; too much parallelism is wasted, poor locality stalls on memory, and extra recomputation can trade one for the other.
  • Decoupled compute in Halide: describing an image-processing pipeline as pure functions (the algorithm) separately from tiling, fusion, vectorization and storage granularity choices (the schedule) let Halide match or beat hand-tuned C++ and GPU code with far less code.
  • Model-based, heuristic, and exhaustive search limitations: analytic performance models omit details that turn out to matter, hard-coded heuristics (like a sort's magic size-16 cutoff) go stale as hardware changes, and exhaustive search is often computationally infeasible.
  • Autotuning (OpenTuner): an ensemble search strategy that samples the schedule space, measures real performance, and adaptively favors techniques (such as hill climbing or random search) that are currently working, finding good schedules far faster than exhaustive search and sometimes better than hand-tuned ones.

Walkthrough

Why domain-specific languages help performance (2:05)

The lecture opens by contrasting general-purpose languages, which must support everything and so can't exploit domain structure, with DSLs that make a domain's properties explicit. This gives engineering benefits like clarity and testability, and lets compilers apply domain-specific algebraic simplifications and idioms that would be too narrow to build into C++. It also lets a compiler own performance decisions that would otherwise become brittle hand-tuned code that ages badly as architectures change.

GraphIt and the shape of graph algorithms (6:08)

Using PageRank as a running example, the lecture surveys where large-scale graph processing shows up (search, maps, recommendations, fraud detection) and distinguishes topology-driven algorithms, which touch the whole graph, from data-driven algorithms, which only traverse a local region. It walks through push versus pull update strategies and their different race conditions and inefficiencies, then discusses how graph shape (power-law social networks versus bounded-degree road networks) changes which optimization performs best, motivating a search over a large optimization trade-off space rather than a single fixed strategy.

Writing GraphIt: algorithm and schedule (24:18)

The lecture shows GraphIt's algorithm language, where operations like edge-set apply and vertex-set apply express PageRank concisely, and then its separate schedule language, where annotations select push versus pull traversal, parallelization, and partitioning without touching the algorithm code. Benchmark results show GraphIt's flexibility lets it stay competitive across many different graphs and algorithms, where other frameworks with fixed built-in strategies are fast on some inputs and much slower on others.

Halide: decoupling algorithm from schedule for images (33:30)

Turning to image processing, the lecture introduces Halide with a 3x3 blur example: a straightforward C implementation is far slower than a hand-tuned version using tiling, loop fusion, and vectorization, but Halide's schedule language lets a programmer reach that hand-tuned performance from a much simpler pipeline description. A case study describes an Adobe engineer needing months to hand-optimize a local Laplacian filter, while a Halide reimplementation beat that performance in one day and, once ported to GPU, ran nine times faster still.

Balancing parallelism, locality, and redundant work in Halide schedules (38:39)

The lecture works through concrete scheduling choices: how much parallelism is useful given the number of cores, how loop traversal order affects whether data stays in cache, and how recomputing values redundantly at boundaries can sometimes be cheaper than synchronizing between parallel workers. It frames storage granularity and computation granularity as a spectrum between fully redundant recomputation and fully cached storage, with real pipelines landing at different points depending on the stage.

Real-world adoption and comparison across domains (48:47)

The lecture notes Halide's adoption in Google's YouTube video pipeline, Android camera processing, Adobe Photoshop, and Qualcomm's Snapdragon image processor, framing these as evidence that schedule-based DSLs solve a real industrial problem. A discussion with students draws out the shared pattern between GraphIt and Halide and considers where similar approaches might help, such as scientific computing domains with similarly code-heavy but understaffed performance needs.

From heuristics to autotuning (59:59)

The lecture contrasts three ways to pick tuning parameters: analytic models that omit important effects, hard-coded heuristics that go stale (illustrated by a sort routine's fixed size-16 cutoff dating to 1995), and exhaustive search that's often too slow. It introduces autotuning, as implemented in OpenTuner, which samples the search space, measures real runtime, and adaptively shifts effort toward techniques currently finding improvements, closing with a case where OpenTuner found GraphIt schedules faster than exhaustive search and occasionally better than hand-picked ones.

Before you watch

  • Familiarity with parallel loops, races, and locks helps, since push/pull graph updates and Halide scheduling both hinge on these ideas.
  • Having used or read about OpenTuner from earlier assignments in this course gives useful context for the autotuning section.
  • No prior graph algorithms or image processing background is assumed; the lecture introduces both.

Check your understanding

  1. Why does separating "algorithm" from "schedule" make performance tuning easier in both GraphIt and Halide?
  2. What is the trade-off between push-style and pull-style graph updates, and why does pull avoid needing atomic operations?
  3. Why can graph partitioning improve locality while also adding overhead or reducing parallelism?
  4. In Halide's parallelism/locality/redundant-work trade-off, give an example where doing extra (redundant) computation can improve overall performance.
  5. Why did a hard-coded heuristic like a sort algorithm's size-16 cutoff stop being a good choice over time, and how does autotuning avoid this problem?

Vocabulary

domain-specific language (DSL) (noun)
A programming language designed for one particular kind of problem instead of general use.
GraphIt is a domain-specific language for graph processing.
hand-optimized (adjective)
Improved manually by a programmer, rather than automatically by a tool.
The goal is near-hand-optimized performance without manual tuning for every machine.
algorithm vs. schedule (phrase)
A separation between what a program computes and how it is executed on hardware.
Splitting algorithm vs. schedule lets you change performance without rewriting the logic.
performance portability (noun)
The ability of code to run fast on different kinds of hardware without being rewritten.
Separating schedule from algorithm improves performance portability.
PageRank (noun)
An algorithm that ranks items, such as web pages, by how important their connections are.
PageRank is used as a running example of a graph algorithm.
topology-driven (adjective)
Describing an algorithm that processes the entire structure of a graph.
Topology-driven algorithms touch every vertex and edge of the graph.
data-driven (adjective)
Describing an algorithm that only processes the parts of the data relevant to the current task.
Data-driven algorithms traverse only a local region of the graph.
push (traversal) (noun)
A graph update method where a vertex sends updates to its neighbors.
The push traversal needs atomic updates to avoid race conditions.
pull (traversal) (noun)
A graph update method where a vertex reads updates from its neighbors instead of sending them.
Pull traversal avoids races but may check neighbors with nothing new.
race condition (noun)
A bug that happens when multiple threads access shared data at the same time in an unsafe way.
Push-style updates risk a race condition without atomic operations.
graph partitioning (noun)
Splitting a large graph into smaller pieces to improve performance.
Graph partitioning can improve locality but adds bookkeeping overhead.
trade-off (noun)
A balance between two good things where improving one can make the other worse.
Every optimization here involves a trade-off between speed and complexity.
locality (noun)
How close together in memory the data a program uses tends to be.
Better locality means fewer slow trips to main memory.
redundant work (noun)
Extra computation that repeats something already done, often to avoid a different cost.
Sometimes redundant work is cheaper than coordinating between threads.
pipeline (noun)
A sequence of processing steps where the output of one stage feeds into the next.
Halide describes an image-processing pipeline as a chain of pure functions.
tiling (noun)
Splitting data or loops into smaller blocks that fit better in cache.
Tiling improves the blur algorithm's speed by fitting data into cache.
loop fusion (noun)
Combining multiple separate loops into one loop to reduce overhead.
Loop fusion reduces the number of passes over the image data.
vectorization (noun)
Rewriting code so multiple data values are processed with a single instruction.
Vectorization speeds up the blur filter by handling several pixels at once.
storage granularity (noun)
The size of the chunks of data that are stored or recomputed at each step.
Choosing storage granularity balances memory use against recomputation.
analytic performance model (noun)
A mathematical model that predicts a program's speed without running it.
An analytic performance model can miss important real-world details.
heuristic (noun)
A practical rule that usually works well but is not guaranteed to be optimal.
A hard-coded heuristic like a fixed cutoff can become outdated.
exhaustive search (noun)
A search method that tries every possible option to find the best one.
Exhaustive search over all schedules is often too slow to be practical.
autotuning (noun)
Automatically searching for good settings by measuring real performance.
OpenTuner uses autotuning to find fast schedules without manual tuning.
hill climbing (noun)
A search strategy that makes small improvements step by step toward a better solution.
Hill climbing is one of the techniques OpenTuner's ensemble search uses.
ensemble search (noun)
A search strategy that combines several different methods and favors whichever is working best.
OpenTuner's ensemble search adaptively shifts effort between techniques.

Chapters

From the YouTube description

MIT 6.172 Performance Engineering of Software Systems, Fall 2018
Instructor: Saman Amarasinghe
View the complete course: https://ocw.mit.edu/6-172F18
YouTube Playlist: https://www.youtube.com/playlist?list=PLUl4u3cNGP63VIBQVWguXxZZi0566y7Wf

Prof. Amarasinghe discusses domain specific languages, when they are applicable to be used, scheduling optimizations, and autotuning. The three languages discussed in particular are GraphIt, Halide, and OpenTuner.

License: Creative Commons BY-NC-SA
More information at https://ocw.mit.edu/terms
More courses at https://ocw.mit.edu

← Lecture 17: Synchronization Without Locks · Lecture 19: Leiserchess Codewalk →