How GIMP Distributes Processing Tiles Across Threads
This article examines how the GNU Image Manipulation Program (GIMP) distributes image processing tasks across multiple CPU cores. By relying on the Generic Graphics Library (GEGL) as its core engine, GIMP breaks high-resolution images into manageable, discrete chunks called tiles and dispatches them across a thread pool. The following sections break down the underlying architecture, spatial subdivision techniques, graph dependency scheduling, and dynamic load-balancing algorithms that govern parallel tile execution in GIMP.
GEGL and the 128x128 Tile Architecture
GIMP delegates all non-destructive pixel manipulation and rendering
to GEGL. At the core of GEGL’s memory management is
GeglBuffer, which divides arbitrary image dimensions into
uniform, square tiles—typically 128x128 pixels.
This fixed-tile granularity provides the baseline unit of parallelization. By isolating spatial regions into independent memory buffers, adjacent threads can read from and write to separate tiles concurrently without invoking expensive locking mechanisms on the pixel level.
Spatial Decomposition and Chunk Splitting
When an operation or a display refresh requests a region of interest,
GIMP defines that area as a bounding box (GeglRectangle).
Before threads are allocated, the target area undergoes spatial
decomposition:
- Alignment to Tile Boundaries: The requested bounding box is mapped to the underlying grid of 128x128 tiles.
- Sub-Division: If a processing operation is computationally dense or spans multiple tiles, the scheduler fragments the work into linear chunks or single-tile tasks.
- Scanline Partitioning: For operations that require neighborhood pixel access (e.g., convolution kernels or Gaussian blurs), tasks are grouped along scanline bands to maximize CPU cache locality (L1/L2 cache affinity) before distribution to worker threads.
Dynamic Self-Scheduling Algorithm
Instead of relying on static allocation—where each thread is pre-assigned a fixed number of tiles—GIMP and GEGL primarily employ a dynamic self-scheduling algorithm (similar to guided or dynamic scheduling in OpenMP).
Because image operations vary in complexity across an image (for instance, an edge-detection filter executes faster on uniform backgrounds than on intricate details), static assignment creates idle-thread bottlenecks. To mitigate this:
- Centralized Task Distribution: Processing coordinates for dirty tiles are inserted into a shared, thread-safe queue.
- Worker Thread Pull Model: Worker threads spawned within GEGL’s thread pool continuously query the queue for the next available tile coordinates.
- Atomic Offsets: Workers use atomic operations to increment a tile index counter, ensuring minimal synchronization overhead while avoiding race conditions.
- Work Stealing: When uneven workloads leave some threads starved while others complete long-running spatial regions, idle threads can pull residual tiles from active branch queues.
Directed Acyclic Graph (DAG) Traversal
GIMP’s operations are modeled as a Directed Acyclic Graph, where nodes represent operations (e.g., color correction, blending) and edges represent image data pipelines. Tile distribution is therefore constrained by topological dependencies:
- Pull-Based Evaluation: Rendering is initiated from the sink node (such as the display or an export function), requesting specific rectangular regions upstream.
- Dependency Resolution: A tile task at Node B cannot be enqueued into the worker pool until the corresponding spatial tiles from input Node A are fully computed and resident in cache or RAM.
- Pipelined Execution: As soon as a single 128x128 tile is finished by a thread at an upstream node, that specific tile can be immediately scheduled for processing at the downstream node, enabling vertical pipelining across thread pools rather than waiting for an entire layer to finish.
Thread Pool Management and Thread Throttling
GIMP executes these algorithms using an abstracted thread pool system
implemented via GLib (GThreadPool) or GEGL’s internal
thread abstractions. The size of the pool is determined dynamically at
runtime based on the system's available logical processing cores, though
users can manually limit concurrency within GIMP’s preferences.
The scheduler balances thread saturation against cache thrashing: if an operation is memory-bandwidth bound rather than compute-bound, creating an excessive number of tile-processing threads can degrade performance through memory bus saturation. Consequently, GEGL’s scheduler adjusts chunk sizes dynamically to balance CPU utilization and cache coherence.