# Why is there a performance penalty in evaluating the axes of an OffsetArray?

**URL:** <https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542>\
**Category:** Performance\
**Tags:** question, performance\
**Created:** [October 17, 2020, 1:34pm UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542 "2020-10-17T13:34:11Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [October 17, 2020, 1:34pm UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542/1 "2020-10-17T13:34:12Z")

</div>

Currently evaluating the `axes` of an `OffsetArray` is considerably slower than that of an `Array` as the former uses a custom axis type that wraps the axes of the parent. I am trying to understand how to improve the performance of this operation.

To give an example (using the master branch of `OffsetArrays.jl`):

```julia
julia> X = rand(4, 4, 4, 4, 4, 4);

julia> XO = OffsetArray(X, -1, -2, -3, 1, 2, 3);

julia> @btime axes($X);
  4.286 ns (0 allocations: 0 bytes)

julia> @btime axes($XO);
  9.056 ns (0 allocations: 0 bytes)

```

Axes in either case are constructed using a `map`, and the performance of the `map` is identical as expected.

```julia
# Axes for an Array
julia> @btime map(Base.OneTo, size($X));
  4.288 ns (0 allocations: 0 bytes)

# Axes for an OffsetArray
julia> @btime map(OffsetArrays.IdOffsetRange, axes(parent($XO)), $XO.offsets);
  9.310 ns (0 allocations: 0 bytes)

```

The performance penalty appears to arise in constructing the type, and not in evaluating the axes of the parent. We may check this as

```julia
julia> axOp = axes(parent(XO));

julia> @btime map(OffsetArrays.IdOffsetRange, $axOp, $XO.offsets);
  9.036 ns (0 allocations: 0 bytes)

```

We check the performance of the constructors:

```julia
julia> @btime OffsetArrays.IdOffsetRange($(Ref(Base.OneTo(4)))[], $(Ref(0))[]);
  3.217 ns (0 allocations: 0 bytes)

julia> @btime Base.OneTo($(Ref(4))[]);
  2.717 ns (0 allocations: 0 bytes)

# Constructing tuples of these types
julia> @btime ntuple(x->$(Ref(Base.OneTo(4)))[], 6);
  3.221 ns (0 allocations: 0 bytes)

julia> @btime ntuple(x->$(Ref(OffsetArrays.IdOffsetRange(Base.OneTo(4),0)))[], 6);
  4.261 ns (0 allocations: 0 bytes)

```

I’m not sure if there’s much difference here. Why is the map so slow, and how to improve the performance of this operation?

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [October 18, 2020, 11:02am UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542/2 "2020-10-18T11:02:05Z")

</div>

Please try a more involved benchmark, I am not sure that timings on the order of nanoseconds are very meaningful.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [October 18, 2020, 1:01pm UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542/3 "2020-10-18T13:01:10Z")

</div>

Yep. LICM generally makes this kind of overhead irrelevant for any real-world example.

[https://compileroptimizations.com/category/hoisting.htm](https://compileroptimizations.com/category/hoisting.htm)

> **[Loop-invariant code motion](https://en.wikipedia.org/wiki/Loop-invariant_code_motion)**
>
> In computer programming, loop-invariant code consists of statements or expressions (in an imperative programming language) which can be moved outside the body of a loop without affecting the semantics of the program. Loop-invariant code motion (also called hoisting or scalar promotion) is a compiler optimization which performs this movement automatically.
> In the following code sample, two optimizations can be applied.
> Although the calculation x = y + z and x \* x is loop-invariant, precautions m...

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [October 18, 2020, 2:28pm UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542/4 "2020-10-18T14:28:50Z")

</div>

There seems to be some overhead to doing writes to random locations.

Also a few places in DynamicGrids.jl gave me small performance improvements indexing into the parent instead of the OffsetArray.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [October 18, 2020, 2:37pm UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542/5 "2020-10-18T14:37:25Z")

</div>

Would be good to document those, as they may be fixable with an `@inline`. Hoisting requires a place to hoist to (i.e., a caller that accesses the array multiple times), and it has to be in the same compiled blob.

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [October 18, 2020, 3:01pm UTC](https://discourse.julialang.org/t/why-is-there-a-performance-penalty-in-evaluating-the-axes-of-an-offsetarray/48542/6 "2020-10-18T15:01:10Z")

</div>

Sure I’ll look into it next time I’m working on that.
