# Is there a maximum(f, op, itrs...) in Julia?

**URL:** https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868
**Category:** General Usage
**Tags:** tullio
**Created:** [July 1, 2021, 2:23am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868 "2021-07-01T02:23:10Z")
**Posts on this page:** 15
**Page:** 1

<div class="post-metadata">

### Author: ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)
#### Post date: [July 1, 2021, 2:23am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/1 "2021-07-01T02:23:10Z")

</div>

Sometimes I see the following pattern in code. How can I write this in a more-efficient/elegant way? Without writing explicit loops, of course.

```julia
a = rand(5,5);
b = rand(5,5);
delta = maximum(abs, i-j for (i,j) in zip(a,b))

```

Ideally, I imagine something like:

```julia
 delta = maximum(abs, -, a, b) 

```

Is this possible in Julia?

---

<div class="post-metadata">

### Author: ![jzr](https://avatars.discourse-cdn.com/v4/letter/j/eb9ed0/32.png) [@jzr](https://discourse.julialang.org/u/jzr)
#### Post date: [July 1, 2021, 3:34am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/2 "2021-07-01T03:34:41Z")

</div>

```julia
julia> a = rand(5,5);
julia> b = rand(5,5);
julia> delta = maximum(abs, i-j for (i,j) in zip(a,b))
0.8146851910185748
julia> maximum(abs, a .- b)
0.8146851910185748

```

---

<div class="post-metadata">

### Author: ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)
#### Post date: [July 1, 2021, 3:34am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/3 "2021-07-01T03:34:52Z")

</div>

Yes, `delta = mapreduce(abs∘-, max, a, b)`. However, this doesn’t (yet) have a fast implementation, so the `zip` may be better.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [July 1, 2021, 5:14am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/4 "2021-07-01T05:14:23Z")

</div>

```julia
maximum(x->abs(x[1]-x[2]), zip(a, b))

```

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [July 1, 2021, 10:56am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/5 "2021-07-01T10:56:56Z")

</div>

@jzr, the dot seems to be unnecessary? Same result with: `maximum(abs, a - b)`

---

<div class="post-metadata">

### Author: ![genkuroki](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/genkuroki/32/18030_2.png) [@genkuroki](https://discourse.julialang.org/u/genkuroki)
#### Post date: [July 1, 2021, 11:11am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/6 "2021-07-01T11:11:14Z")

</div>

```julia
maximum(Base.splat(abs∘-), zip(a, b))

```

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [July 1, 2021, 11:12am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/7 "2021-07-01T11:12:37Z")

</div>

> [@mcabbott](#):
>
> Yes, `delta = mapreduce(abs∘-, max, a, b)` . However, this doesn’t (yet) have a fast implementation, so the `zip` may be better.

What does a “fast implementation” mean in this context? Is `mapreduce` implemented in a not very efficient way and there is potential to improve it?

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [July 1, 2021, 11:22am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/8 "2021-07-01T11:22:54Z")

</div>

No, `mapreduce` is pretty well optimized, but only for specific cases since we can dispatch on different functions for those (functions have their own type after all). `abs∘-` is unlikely to be one of those cases so far, so it’ll fall back to a generic implementation (which may not SIMD etc.) Depending on the functions and iterators in use, `mapreduce` may be specialized to those and achieve a greater speedup than the fallback implementation can provide.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [July 1, 2021, 11:27am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/9 "2021-07-01T11:27:12Z")

</div>

Fastest I’ve seen so far:

```julia
jl> using Tullio

jl> @tullio (max) maxval := abs(a[i] - b[i])

```

@mcabbott must there be an output variable inside the macro call? Could one hypothetically write

```julia
jl> maxval = @tullio (max) abs(a[i] - b[i])

```

?

(Oops: fixed missing `abs`)

---

<div class="post-metadata">

### Author: ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)
#### Post date: [July 1, 2021, 11:48am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/10 "2021-07-01T11:48:19Z")

</div>

> [@DNF](#):
>
> must there be an output variable inside the macro call?

For now there must be, although it could be changed I suppose. You can write `@tullio (max) _ := abs(a[i] - b[i])` which at least saves thinking up a name.

> What does a “fast implementation” mean in this context?

If you do `@less mapreduce(abs∘-, max, a, b)` you will see this:

```julia
mapreduce(f, op, A::AbstractArrayOrBroadcasted...; kw...) =
    reduce(op, map(f, A...); kw...)

```

i.e. it performs `map` which allocates the array, then `reduce`. I think this is a placeholder implementation, to allow the syntax now and make it fast later. It’s very much like `maximum(abs.(a.-b))`, while with one array `maximum(abs, a)` is `mapreduce(abs, max, a)` and this does _not_ allocate `abs.(a)`. The issue about the multiple-argument case is [#38558](https://github.com/JuliaLang/julia/issues/38558).

---

<div class="post-metadata">

### Author: ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)
#### Post date: [July 1, 2021, 4:00pm UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/11 "2021-07-01T16:00:28Z")

</div>

Thank you all for your contributions, here I present some benchmarks for different alternatives. Tullio seems to be the only alternative with the same performance as explicit loops.

```julia
function max_abs(a,b)
    mx = 0.0
    for i in eachindex(a,b)
        tmp = abs(a[i]-b[i]) 
        tmp > mx && (mx = tmp)
    end 
    mx 
end 

N = 500
a = rand(N,N)
b = rand(N,N)

@btime max_abs($a,$b) # 188.500 μs (0 allocations: 0 bytes)
@btime maximum(abs(i-j) for (i,j) in zip($a,$b)) # 442.200 μs (0 allocations: 0 bytes)
@btime maximum(abs, $a - $b) # 568.800 μs (2 allocations: 1.91 MiB)
@btime maximum(Base.splat(abs∘-), zip($a, $b)) # 442.300 μs (0 allocations: 0 bytes)
@btime mapreduce(abs∘-, max, $a, $b) # 572.900 μs (2 allocations: 1.91 MiB)
using Tullio
@btime @tullio (max) delta := abs($a[i] - $b[i]) # 188.800 μs (1 allocation: 16 bytes)
using LoopVectorization
@btime @tullio (max) delta := abs($a[i] - $b[i]) # 75.300 μs (1 allocation: 16 bytes)

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [July 1, 2021, 5:15pm UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/13 "2021-07-01T17:15:40Z")

</div>

> [@Seif\_Shebl](#):
>
> `tmp > mx && (mx = tmp)`

Not sure if it will make a difference, but I would use

```julia
mx = max(mx, tmp) 

```

to avoid the branch, which could in principle prevent vectorization. It’s also clearer.

---

<div class="post-metadata">

### Author: ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)
#### Post date: [July 1, 2021, 5:41pm UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/14 "2021-07-01T17:41:41Z")

</div>

> [@DNF](#):
>
> `mx = max(mx, tmp) `

This is nicer undoubtedly, but the performance drops to the generator version, it needs a `@fastmath` to keep the same performance in this case.

---

<div class="post-metadata">

### Author: ![genkuroki](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/genkuroki/32/18030_2.png) [@genkuroki](https://discourse.julialang.org/u/genkuroki)
#### Post date: [July 2, 2021, 7:04am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/15 "2021-07-02T07:04:49Z")

</div>

An extended version of Seif Shebl’s excellent benchmark test

I tried the benchmark tests for N = 100, 200, 500, 1000, and 2000.

The results look different for each N.

Julia script:

```julia
# maxabstest.jl

using BenchmarkTools

N = isempty(ARGS) ? 500 : parse(Int, ARGS[1])

function max_abs(a, b)
    m = zero(promote_type(eltype(a), eltype(b)))
    for i in eachindex(a, b)
        tmp = abs(a[i] - b[i]) 
        tmp > m && (m = tmp)
    end 
    m
end

a = randn(N, N)
b = randn(N, N)

@show VERSION
@show Threads.nthreads()
println()
@show N
print("simple for loop: ")
@btime max_abs($a, $b)
print("mapreduce abs∘- max: ")
@btime mapreduce(abs∘-, max, $a, $b)
print("maximum(abs, a - b): ")
@btime maximum(abs, $a - $b)
print("maximum(generator): ")
@btime maximum(abs(i - j) for (i, j) in zip($a, $b))
print("maximum splat(abs∘-):")
@btime maximum(Base.splat(abs∘-), zip($a, $b))
using Tullio
print("Tullio: ")
@btime @tullio (max) _ := abs($a[i] - $b[i])
print("Tullio (LoopVect.): ")
using LoopVectorization
@btime @tullio (max) _ := abs($a[i] - $b[i])

function max_abs_turbo(a, b)
    m = zero(promote_type(eltype(a), eltype(b)))
    @turbo for i in eachindex(a, b)
        m = max(m, abs(a[i] - b[i]))
    end 
    m
end

function max_abs_tturbo(a, b)
    m = zero(promote_type(eltype(a), eltype(b)))
    @tturbo for i in eachindex(a, b)
        m = max(m, abs(a[i] - b[i]))
    end 
    m
end

print("LoopVect. @turbo: ")
@btime max_abs_turbo($a, $b)
print("LoopVect. @tturbo: ")
@btime max_abs_tturbo($a, $b)

```

Results for N = 100, 200, 500, 1000, 2000

```julia
$ julia maxabstest.jl 100
VERSION = v"1.6.1"
Threads.nthreads() = 12

N = 100
simple for loop: 8.733 μs (0 allocations: 0 bytes)
maximum(abs, a - b): 10.100 μs (2 allocations: 78.20 KiB)
maximum(generator): 18.100 μs (0 allocations: 0 bytes)
maximum splat(abs∘-): 18.100 μs (0 allocations: 0 bytes)
mapreduce abs∘- max: 11.300 μs (2 allocations: 78.20 KiB)
Tullio: 8.767 μs (1 allocation: 16 bytes)
Tullio (LoopVect.): 1.290 μs (1 allocation: 16 bytes)
LoopVect. @turbo: 1.240 μs (0 allocations: 0 bytes)
LoopVect. @tturbo: 722.689 ns (0 allocations: 0 bytes)

```

```julia
$ julia maxabstest.jl 200
VERSION = v"1.6.1"
Threads.nthreads() = 12

N = 200
simple for loop: 34.900 μs (0 allocations: 0 bytes)
maximum(abs, a - b): 34.500 μs (2 allocations: 312.58 KiB)
maximum(generator): 71.600 μs (0 allocations: 0 bytes)
maximum splat(abs∘-): 71.700 μs (0 allocations: 0 bytes)
mapreduce abs∘- max: 42.300 μs (2 allocations: 312.58 KiB)
Tullio: 34.900 μs (1 allocation: 16 bytes)
Tullio (LoopVect.): 8.400 μs (1 allocation: 16 bytes)
LoopVect. @turbo: 9.600 μs (0 allocations: 0 bytes)
LoopVect. @tturbo: 2.010 μs (0 allocations: 0 bytes)

```

```julia
$ julia maxabstest.jl 500
VERSION = v"1.6.1"
Threads.nthreads() = 12

N = 500
simple for loop: 213.400 μs (0 allocations: 0 bytes)
maximum(abs, a - b): 444.500 μs (2 allocations: 1.91 MiB)
maximum(generator): 447.000 μs (0 allocations: 0 bytes)
maximum splat(abs∘-): 447.100 μs (0 allocations: 0 bytes)
mapreduce abs∘- max: 454.600 μs (2 allocations: 1.91 MiB)
Tullio: 213.400 μs (1 allocation: 16 bytes)
Tullio (LoopVect.): 55.900 μs (1 allocation: 16 bytes)
LoopVect. @turbo: 56.100 μs (0 allocations: 0 bytes)
LoopVect. @tturbo: 12.700 μs (0 allocations: 0 bytes)

```

```julia
$ julia maxabstest.jl 1000
VERSION = v"1.6.1"
Threads.nthreads() = 12

N = 1000
simple for loop: 1.053 ms (0 allocations: 0 bytes)
mapreduce abs∘- max: 2.354 ms (2 allocations: 7.63 MiB)
maximum(abs, a - b): 2.240 ms (2 allocations: 7.63 MiB)
maximum(generator): 1.939 ms (0 allocations: 0 bytes)
maximum splat(abs∘-): 1.937 ms (0 allocations: 0 bytes)
Tullio: 263.000 μs (93 allocations: 4.73 KiB)
Tullio (LoopVect.): 257.600 μs (93 allocations: 4.73 KiB)
LoopVect. @turbo: 410.200 μs (0 allocations: 0 bytes)
LoopVect. @tturbo: 207.600 μs (0 allocations: 0 bytes)

```

```julia
$ julia maxabstest.jl 2000
VERSION = v"1.6.1"
Threads.nthreads() = 12

N = 2000
simple for loop: 4.193 ms (0 allocations: 0 bytes)
mapreduce abs∘- max: 10.075 ms (2 allocations: 30.52 MiB)
maximum(abs, a - b): 11.004 ms (2 allocations: 30.52 MiB)
maximum(generator): 7.903 ms (0 allocations: 0 bytes)
maximum splat(abs∘-): 7.896 ms (0 allocations: 0 bytes)
Tullio: 2.388 ms (199 allocations: 10.17 KiB)
Tullio (LoopVect.): 2.855 ms (200 allocations: 10.20 KiB)
LoopVect. @turbo: 2.540 ms (0 allocations: 0 bytes)
LoopVect. @tturbo: 2.275 ms (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

### Author: ![jzr](https://avatars.discourse-cdn.com/v4/letter/j/eb9ed0/32.png) [@jzr](https://discourse.julialang.org/u/jzr)
#### Post date: [July 2, 2021, 7:36am UTC](https://discourse.julialang.org/t/is-there-a-maximum-f-op-itrs-in-julia/63868/16 "2021-07-02T07:36:18Z")

</div>

Just to mention another option [Home · Transducers.jl](https://juliafolds.github.io/Transducers.jl/dev/) supports map/reduce operations.
