Skip to content

libvips demand-driven execution model

Copy page

libvips is not a traditional image library that reads in rows and processes them one at a time. It is a demand-driven execution engine — operations you apply to an image are only performed together when someone actually requests pixels. This design is both the source of libvips’ speed and the cause of several common misunderstandings.

You built a pipeline, but nothing happened

Section titled “You built a pipeline, but nothing happened”

This code:

image, err := vips.NewImageFromFile("input.jpg")
if err != nil {
return err
}
defer image.Close()
if err := image.Resize(0.5, vips.KernelLanczos3); err != nil {
return err
}
if err := image.GaussianBlur(3.0); err != nil {
return err
}

The loader normally reads enough data to identify the format and metadata, while subsequent calls build a processing graph: input.jpg → Load → Resize(0.5) → GaussianBlur(3.0) → (waiting for demand). The complete result has not been evaluated and stored. An export or write requests the required regions from downstream to upstream.

These actions trigger real evaluation:

Trigger Explanation
ExportJpeg() / ExportWebp() etc. Encoder reads rows from the result one at a time
SaveToWriter() / TranscodeStream() Write target requests pixels in chunks
Another image reads from this one (composite, join) Downstream operation triggers upstream evaluation

libvips does not cache the whole image in memory at once. It keeps a line buffer cache — the small set of rows needed to satisfy the current region request.

This means:

  • If you read top-to-bottom sequentially, libvips only needs a few rows in memory.
  • If you need random access (e.g., 90° rotation needs the last row first), libvips may need to cache the whole input — or fail with out of order read.

libvips supports two access modes:

Mode Behaviour Peak RAM
Random (default) Any region may be read in any order Cached regions of the input
Sequential Top-to-bottom, single-pass only Dominated by image width, operation windows, and codec buffering

Sequential mode must be enabled explicitly (access="sequential" or [access=sequential]), and it restricts what operations you can do — rotation, vertical flip, and other non-linear reads will fail.

// Downsample first when the product permits blurring at lower resolution.
if err := image.Resize(0.1, vips.KernelLanczos3); err != nil {
return err
}
if err := image.GaussianBlur(5.0); err != nil {
return err
}
// If the product requires a full-resolution blur, the order cannot be
// exchanged and the operation will cost more.

Downsampling first means every subsequent operation works on fewer pixels. This principle applies to any image library, but is especially important in libvips because the operation graph determines the actual computation at evaluation time.

  • Myth 1: “vips.Startup() loads an image.” No — it only initialises the libvips runtime.
  • Myth 2: “The image is processed before Close().” Processing happens on demand during export; Close just releases the image reference.
  • Myth 3: “Every operation creates a complete intermediate image.” libvips normally evaluates chained operations by region; materialization depends on access patterns.
  • Myth 4: “Demand-driven means slow.” The opposite — demand-driven means your program only computes pixels it actually needs.
  • Out of Order Read: Random-access operations on sequential images will fail.
  • Cache Miss Penalty: Random access on large images is significantly slower than sequential.
  • Binding default differences: govips defaults to random; sharp configures sequentialRead; pyvips configures access.

Learn about sequential and random access in detail.