JuliaArrays / JuliaArrays/MappedArrays.jl

`iterate` fallback causes 5X performance drop

Open
#45 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Julia
Stars
88
Forks
15
PR merge metrics
No merged PRs in 30d

Description

Consider a "function" that has two versions. foo3 is just foo2, but it accepts a function in the first argument the same way that e.g. sum does.

function foo2(a)
    r = nothing
    for y in a
        if r === nothing
            r = y
        else
            r += y
        end
    end
    return r
end

function foo3(f, a)
    r = nothing
    for x in a
        y = f(x)
        if r === nothing
            r = y
        else
            r += y
        end
    end
    return r
end

I personally do not like the pattern, and MappedArrays.jl gives me a way of factoring out that function so that I can only write foo2, even if I, say, need to do the computation only on a specific field of each element.

using MappedArrays: mappedarray

const A = [(field=i,) for i = 1:1000]
const f = Base.Fix2(getfield, :field)

foo2(mappedarray(f, A)) == foo3(f, A) || error()

But the performance is not equivalent:

julia> using BenchmarkTools

julia> @benchmark foo2(mappedarray(f, A))

BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     325.878 ns (0.00% GC)
  median time:      409.595 ns (0.00% GC)
  mean time:        430.838 ns (0.00% GC)
  maximum time:     1.315 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     221

julia> @benchmark foo3(f, A)
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     63.348 ns (0.00% GC)
  median time:      68.360 ns (0.00% GC)
  mean time:        71.576 ns (0.00% GC)
  maximum time:     266.343 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     972

The fallback on iterate(::AbstractArray, ...) appears to be responsible. Defining

import MappedArrays

function Base.iterate(A::MappedArrays.ReadonlyMappedArray, args...)
    r = iterate(A.data, args...)
    r === nothing && return r
    return (A.f(r[1]), r[2])
end

eliminates the gap!

Defining iterate on MappedArray is straightforward, too. I'm not sure what to do for the "Multi" types.

(originally from https://github.com/goretkin/FactorPerElementFunctionPerformance and https://discourse.julialang.org/t/factor-out-field-access-of-abstractarray-and-or-iterator-interface/41493)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Base.iterate fallback for MappedArrays.ReadonlyMappedArray and compare it with the shown specialized implementation. Check the corresponding MappedArray and Multi types, then benchmark foo2(mappedarray(f, A)) against foo3(f, A) to confirm the performance gap is addressed for the supported types.

Written by the indexing model from the issue text.

Assessment

Tech stack
julia
Domain
performance
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.