Hi, I just wrote another tutorial on data-parallel programming in Julia: Efficient and safe approaches to mutation in data parallelism. This is a sequel of A quick introduction to data parallelism in Julia.
As discussed in a quick introduction to data parallelism, data parallel style lets us write fast, portable, and generic parallel programs. One of the main focuses was to unlearn the “sequential idiom” that accumulates the result into mutable state. However, mutable state is sometimes preferred for efficiency. After all, a fast parallel program is typically a composition of fast sequential programs. Furthermore, managing mutable states is sometimes unavoidable for interoperability with libraries preferring or requiring mutation-based API. However, sharing mutable state is almost always a bad idea. Naively doing so likely results in data races and hence programs with undefined behaviors. Although low-level concurrency APIs such as locks and atomics can be used for writing (typically) inefficient but technically correct programs, a better approach is to use single-owner local mutable state. In particular, we will see that unlearning sequential idiom was worth the effort since it points us to what we call ownership-passing style that can be used to construct mutation-based parallel reduction from mutation-free (“purely functional”) reduction as an optimization.
This tutorial provides an overview of the mutable object handling in data-parallel Julia programs. It also discusses the effect and analysis of false sharing which is a major performance pitfall when using in-place operations in a parallel program.
Table of contents:
- Example: multiplying and adding matrices
- Categorizing mutation use-cases
- Filling outputs
- In-place reductions
- Flexible reduction with
- Initializing mutable accumulator using
- Combining containers
- Pitfalls with mutable reduction states
- Mutable temporary objects (private variables)
- Accidental mutations
- Advanced/Performance: False sharing
- Advanced: adjoining trick