Skip to content

Performance regression when partially applying types #9

@arikheinss

Description

@arikheinss

Hello.

I have noticed pretty significant performance regressions when partially applying data types, like convert $ Float64, round $ Int, parse $ Int etc. To reproduce, take this as an example:

using PartialFunctions, BenchmarkTools
data = rand(1:20, 1000) .|> string

@btime val_native = mapreduce( x -> parse(Int, x), +, data)
# -> 18.243 μs (1 allocation: 16 bytes)

@btime val_partial = mapreduce( parse $ Int, +, data)
# -> 129.363 μs (958 allocations: 14.98 KiB)

The reason for this can be found when inspecting the type:

typeof(parse $ Int)
# -> PartialFunctions.PartialFunction{nothing, nothing, typeof(parse), Tuple{DataType}, NamedTuple{(), Tuple{}}}

Notice the Tuple{DataType} in there. When compiling all the compiler sees is this signature: parse(::DataType, ::String), which is not enough information to compile type stable code.
The reason that happens is that

typeof(Int) # -> DataType
# same for all other data types

and that DataType is technically a concrete type but practically not really. Ideally this should be replaced with Type{Int}.

One way to fix this would be to supply a new specialized method for the $ function,

($)(f::Function, ::Type{T}) where T = ...

wherein the type parameter is manually set to Type{T}.

This problem has been solved before by the Fix1 type, see here.

Alternatively, I experimentally created my own partial application package. You can have a look how I solved the problem here.

I would offer the PR with a fix myself, but I have had very little time for hobby programming the past weeks, so I decided to just notify you about the problem instead 🤷‍♂️.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions