Skip to content

Slow iteration because of IxDyn #1340

Description

@RunDevelopment

As I described in #1339, an array with IxDyn has 10x slower iteration performance than an equivalent array using a fixed-size index. This has wide-reaching implications, as this means that many pixel-wise operations are substantially slower.

Example:
Let n be an ndarray with the shape (4320, 8468, 4).

let n: ArrayViewD<f32>; // uses IxDyn 

// iter()

// slow: takes 3sec on my machine
let _: Vec<f32> = n.iter().cloned().collect();

// fast: takes 0.4sec on my machine
let n3: ArrayView3<f32> = n.into_dimensionality().unwrap();
let _: Vec<f32> = n3.iter().cloned().collect();

// to_owned()

// slow: takes 0.95sec on my machine
let _ = n.to_owned();

// fast: takes 0.25sec on my machine
let n3: ArrayView3<f32> = n.into_dimensionality().unwrap();
let _ = n3.to_owned();

To improve the performance of arrays using IxDyn, I suggest optimizing iteration for these arrays. Since we can see that using fixed-sized indexes is substantially faster, I suggest internally "casting" the array to a fixed-size index (or similar) before iteration when possible.

Activity

  1. bluss commented on Mar 31, 2024

    @bluss
    Member

    #979 helps for this case, even if it was developed with the general case (any iterator) in mind. ndarray does also already in several cases have fast paths for contiguous arrays, also if they are dynamic dimensional.

  2. robertknight commented on Jun 22, 2025

    @robertknight

    I can reproduce similar timings even when the array is contiguous ("standard layout") and the fast path (which wraps a simple slice iterator) is being used. Interestingly it appears the mere presence of the code to handle the slow case has a significant impact. This slow-case code is different and more difficult to optimize when the array has dynamic rank. When the dynamic rank array is not contiguous, the performance degrades significantly more:

    1. dynamic rank contiguous. mean 340.722ms
    2. dynamic rank non-contiguous. mean 1776.912ms
    3. static rank contiguous. mean 99.790ms
    4. static rank non-contiguous. mean 247.296ms
    5. to slice. mean 38.376ms
    

    Here the "non-contiguous" cases are the result of slicing the (4320, 8468, 4) array using slice_axis_inplace(Axis(1), Slice::new(1, None, 1)). See code. Meanwhile (5) represents doing the fastest possible thing which is a memcpy, and shows why ndarray's documentation recommends using higher-level methods first, as they allow the library to drive the iteration. See also this old post on internal versus external iteration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions