Undef array with customized index

I’m curious if there is a convenient way to define an array with customized index.
In fortran, we have real, dimension(2:6) :: array.
I know in julia we can use Array{Float64}(undef, 10) to declare a 10-element array with undef.
We can then use OffsetArray to get an index starting from any integer.
Is there an easy way like array(Int, -3:3, -5:5) or Int[-3:3, -5:5] to get an array with a customized index?

I am not sure if a helper function exists, but it is not hard to to write one, for example, the one-liner below work if you always use ranges to specify the axes:

oarray(::Type{T}, dims...) where {T} = OffsetArray(Array{T}(undef, length.(dims)...), dims...)

@Henrique_Becker Thanks, this is pretty close to what I want.
I hope this kind of syntax can get support on the language level in the future.

Also, it seems OffsetArray is slower than the standard array when accessing the array elements.
A language-level support should allow the compiler optimizes this away.

OffsetArrays already defines an undef constructor:

julia> OffsetArray{Int}(undef, -3:3, -5:3)
7×9 OffsetArray(::Matrix{Int64}, -3:3, -5:3) with eltype Int64 with indices -3:3×-5:3:
...
1 Like

I don’t know this. Thanks!

Both answers are great.

Is it? Sounds strange to me. I would like to know more about that.

In parallel, note that in Julia there are many ways to iterate over the elements of an array which are independent of the way indices are set. With those the interfaces with arrays of other languages is less prone to error.

for i in eachindex(v)

for (i,el) in pairs(v)

for el in v

for i in axes(v)[1]
1 Like

What I meant is a simple element access like the following.

X = rand(10,10)
XO = OffsetArray(X, -5:4, -5:4)
@btime X[3, 3]   #  14.729 ns (1 allocation: 16 bytes)
@btime XO[3, 3]  #  17.698 ns (1 allocation: 16 bytes)

I’m still using julia 1.5.4. Maybe this is the reason.

1 Like

Your benchmark isn’t measuring what you think–it’s dominated by the fact that X and XO are non-constant global variables. See: GitHub - JuliaCI/BenchmarkTools.jl: A benchmarking framework for the Julia language for more info.

Fixing this gives quite different results (on Julia 1.5.3):


julia> @btime ($X)[3, 3]
  1.753 ns (0 allocations: 0 bytes)
0.34069137833211705

julia> @btime ($XO)[3, 3]
  2.841 ns (0 allocations: 0 bytes)
0.7901133830907208

Updating to Julia 1.6.1 makes OffsetArrays even faster:

julia> @btime ($X)[3, 3]
  1.593 ns (0 allocations: 0 bytes)
0.41741256881369937

julia> @btime ($XO)[3, 3]
  2.250 ns (0 allocations: 0 bytes)
0.4126414953734878

What do you actually mean by this? Most of Julia is implemented in Julia already, so there is no difference between “language-level” code and user code in terms of performance.

3 Likes

Recommended way is axes(v, 1)

1 Like

While there is a small overhead in the “raw” indexing while accessing each individual element, this often does not matter much in practical applications when you access a large number of elements in a loop (see this issue in OffsetArrays.jl). Such differences, when the exist, are perhaps due to the bounds-checking being sub-optimal (there is another issue in OffsetArrays where we are trying to improve this).

Here is an example:

julia> using OffsetArrays

julia> f(x) = sum(xi for xi in x);

julia> g(x) = sum(x[i] for i in eachindex(x));

julia> g2(x) = sum((@inbounds x[i]) for i in eachindex(x));

julia> A = ones(2000,2000);

julia> AO = ones(1:2000, 1:2000);

julia> @btime f($A);
  6.686 ms (0 allocations: 0 bytes)

julia> @btime f($AO);
  6.754 ms (0 allocations: 0 bytes)

julia> @btime g($A);
  6.738 ms (0 allocations: 0 bytes)

julia> @btime g($AO);
  6.791 ms (0 allocations: 0 bytes)

julia> @btime g2($A);
  6.727 ms (0 allocations: 0 bytes)

julia> @btime g2($AO);
  6.724 ms (0 allocations: 0 bytes)

We see that with bounds-checking turned off, there is no performance gap anymore.

To answer the original question, you should be able to use the OffsetArray constructor to initialize an undefined array.

julia> OffsetArray{Float64}(undef, 2:3, 4:5)
2×2 OffsetArray(::Matrix{Float64}, 2:3, 4:5) with eltype Float64 with indices 2:3×4:5:
 0.0  0.0
 0.0  0.0

Another useful function is similar, which chooses an appropriate array type for you depending on the axes.

julia> similar(Array{Float64}, 2:3, 4:5)
2×2 OffsetArray(::Matrix{Float64}, 2:3, 4:5) with eltype Float64 with indices 2:3×4:5:
 6.94499e-310  6.94499e-310
 6.94499e-310  0.0
4 Likes

@rdeits Thanks, could you please explain why you use ($X) in the benchmark?
I can see the difference (no allocation) but I don’t understand why.

I thought proper language support might make the OffsetArrays more efficient.
I might be wrong, but what causes the different access time?
The difference is more than 0.5ns in your benchmark.
Is it from the bound checking?

@jishnub Thanks, I understand that in practice the overhead is not important.
Thank you for pointing out there are other ways to achieve what I want.

@Skoffer Could you please elaborate on how to do this?

I only meant, that instead of axes(v) [1] one should use axes(v, 1)

@Skoffer Thanks.

Check the BenchmarkTools documentation here for an explanation of what’s going on: GitHub - JuliaCI/BenchmarkTools.jl: A benchmarking framework for the Julia language

@rdeits Thanks! I didn’t know that.