How to make splat, map and tuples be efficient

Generally I want to make

f(g("a"), g("c"), g("k"))

with

f(map(g, ["a","c","k"])...)

be efficient and don’t generate allocations.

And I started experimenting, by creating 3 functions:

function foo_tuple(a,b,c)
       +((a+1,b+2,c+3)...)
end

function foo_arr(a,b,c)
       +([a+1,b+2,c+3]...)
end

function foo(a,b,c)
       +(Tuple(i+j for (i,j) in zip(1:3,(a,b,c)))...)
end

Benchmarks are below:

julia> @btime foo(1,2,3)
  247.813 ns (2 allocations: 112 bytes)
12

julia> @btime foo_arr(1,2,3)
  93.243 ns (1 allocation: 80 bytes)
12

julia> @btime foo_tuple(1,2,3)
  1.600 ns (0 allocations: 0 bytes)
12

And now I have several questions:

  1. Is there an efficient way to make map return tuple?
  2. How can I create tuples from arrays efficiently? Tuple([1,2,3]) is 150 times slower than tuple(1,2,3)

I know, that splat isn’t preferable tool for big number of arguments, but with RGBA it will be cool to know how to pass parameters with this. :smiling_face:

Pass a tuple to map :slight_smile:
As in, f(map(g, ("a","c","k"))...).

Either of these is efficient:

v = [1,2,3]
Tuple{Int,Int,Int}(v)
NTuple{3,Int}(v)
3 Likes

To add an explanation to this:
The main difference (from the compiler’s perspective) between [1,2,3] and (1,2,3) is that the Tuple knows how many elements there are (and their types) while the Vector only preserves the type of its elements. So creating a Tuple from a Vector is inherently a type-unstable operation as there is no way to infer the length of resulting tuple from the Vector’s type information. Splatting arguments into a function call essentially reduces to constructing a tuple of all arguments.
So when splatting a Vector into a function, the compiler cannot know how many arguments there will be, so Julia needs to check at runtime.

3 Likes

Thx. Much appreciated.

1 Like