[ANN] FunctionWranglers.jl - Fast, inlined execution of arrays of functions

The need for this functionality come up recently in this topic:

Given an array of small functions (array length up to 200), each of them had to be computed for a large set of inputs. The problem was that dynamic dispatch took too long compared to the actual calculation.

FunctionWranglers.jl started as a solution of this issue. It uses template (meta)programming to unroll loops that call several functions, and to allow static dispatch/inlining on dinamically provided functions.

The following operations are implemented as of now:

  • smap! maps a single set of arguments using all the functions into a preallocated array.
  • sfindfirst looks for the first function which returns true for the given arguments, and returns its index.
  • sreduce transforms a single value using the composite of the functions, and also allows you to provide extra “context” arguments to the functions.

The smap! function can be used to solve the above issue:

  | | |_| | | | (_| |  |  Version 1.5.2 (2020-09-23)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release

julia> using FunctionWranglers

julia> create_adder(value) = (x) -> x + value
create_adder (generic function with 1 method)

julia> adders = Function[create_adder(i) for i = 1:5]
5-element Array{Function,1}:
 #3 (generic function with 1 method)
 #3 (generic function with 1 method)
 #3 (generic function with 1 method)
 #3 (generic function with 1 method)
 #3 (generic function with 1 method)

julia> w = FunctionWrangler(adders)
FunctionWrangler with 5 items: #3, #3, #3, #3, #3, 

julia> result = zeros(Float64, length(adders))
5-element Array{Float64,1}:

julia> smap!(result, w, 10.0)

julia> result
5-element Array{Float64,1}:

julia> @btime smap!($result, $w, d) setup = (d = rand())
  3.934 ns (0 allocations: 0 bytes)

sfindfirst mimics DOM event handling, sreduce is mostly a robust \circ. They can be used e.g. for inlining user provided callbacks.

Note that the s prefix stays for “switched”, as the role of the functions and the data is reversed compared to the normal use (or for “static”, as the value of package is that it allows static dispatch).

A bit more info can be found in the readme.

I hope you find this helpful!


Could https://github.com/yuyichao/FunctionWrappers.jl be useful here?

1 Like

Yes, it could, and it is. In the motivating example, FunctionWrappers improved performance with 20-30%. FunctionWranglers added another 30-40% over it.

FunctionWrappers is lower-level API that gives you type-stability for a single call. It does not unroll your loops, so it may be better when the list of functions changes often. And it has a ~15ns overhead on every call, which is smaller than a dynamic dispatch, but still a lot compared to a simple calculation.


Oh, very interesting, thanks @tisztamo!